pyspider 是一个用python实现的功能强大的网络爬虫系统,能在浏览器界面上进行脚本的编写,功能的调度和爬取结果的实时查看,后端使用常用的数据库进行爬取结果的存储,还能定时设置任务与任务优先级等。
本篇文章只是对这个框架使用的大体介绍,更多详细信息可见官方文档。
安装过程省略
注意:pyspider 与 python 版本存在关键词冲突等问题,推荐使用python 3.6
在控制台输入命令
pyspider all
这样pyspider就算是跑起来了。
拿这个网页来做例子:www.reeoo.com,爬取上面的数据。
第一次跑起来的时候因为没有任务,界面的列表为空,右边有个Create按钮,点击新建任务。
填写完成后,点击Create,便创建成功并跳转到了另一个界面,如下图所示
界面右边区域自动生成了初始默认的代码:
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# Created on 2021-03-28 09:14:23
# Project: reo
from pyspider.libs.base_handler import *
class Handler(BaseHandler):
crawl_config = {
}
@every(minutes=24 * 60)
def on_start(self):
self.crawl('www.reeoo.com', callback=self.index_page)
@config(age=10 * 24 * 60 * 60)
def index_page(self, response):
for each in response.doc('a[href^="http"]').items():
self.crawl(each.attr.href, callback=self.detail_page)
@config(priority=2)
def detail_page(self, response):
return {
"url": response.url,
"title": response.doc('title').text(),
}
点击左边绿色区域右上角的 run 按钮,这时候调用的是 on_start 方法,运行之后页面下册的 follows 按钮出现红色角标
选中 follows 按钮,点击行右侧的运行按钮,这时候调用的是 index_page 方法
运行完成后显示如下图,即 www.reeoo.com
页面上所有的url
此时我们可以任意选择一个结果运行,这时候调用的是 detail_page 方法,返回最终的结果。
结果为json格式的数据,这里我们保存的是网页的 title
和 url
,见左侧黑色的区域
回到主页面,此时看到任务列表显示了我们刚刚创建的任务,设置 status 为 running
,然后点击 Run 按钮执行
执行过程中可以看到整个过程的打印输出
执行完成后,点击 Results 按钮,进入到爬取结果的页面
右上方的按钮选择将结果数据保存成对应的格式,例如:JSON格式的数据为:
以上则为pyspider的基本使用方式。
接下来我们通过自定义来抓取我们需要的数据,目标为抓取这个页面中,每个详情页内容的标题、标签、描述、图片的url、点击图片所跳转的url。
点击首页中的 Create
,新建一个新的脚本myReo
并跳转到脚本的编辑界面
index_page(self, response) 函数为获取到 www.reeoo.com
页面所有信息之后的回调,我们需要在该函数中对 response 进行处理,提取出详情页的url。
通过查看源码,可以发现 class 为 thum 的 div 标签里,所包含的 a 标签的 href 值即为我们需要提取的数据,如下图
代码的实现
@config(age=10 * 24 * 60 * 60)
def index_page(self, response):
for each in response.doc('div[class="thumb"]').items():
detail_url = each('a').attr.href
print(detail_url)
self.crawl(detail_url, callback=self.detail_page)
response.doc(‘div[class=”thumb”]’).items()
返回的是所有 class 为 thumb 的 div 标签,可以通过循环 for…in 进行遍历。each(‘a’).attr.href
对于每个 div 标签,获取它的 a 标签的 href 属性。注意左侧区域下方的几个按钮,可以展示当前所爬取页面的一些信息,web 按钮可以查看当前页面,html 显示当前页面的源码,enable css selector helper 可以通过选中当前页面的元素自动生成对应的 css 选择器方便的插入到脚本代码中,不过并不是总有效,在我们的demo中就是无效的~
接下来开始抓取详情页中的信息,任意选择一条当前的结果,点击运行,如选择第三个
修改 detail_page 函数
@config(priority=2)
def detail_page(self, response):
header = response.doc('body > article > section > article > header')
title = header('h1').text()
time = header('time').text()
tags = []
for each in header.items('a'):
tags.append(each.text())
content = response.doc('div[id="post_content"]')
description = content('blockquote > p').text()
website_url = content('a').attr.href
image_url_list = []
for each in content.items('img[data-src]'):
image_url_list.append(each.attr('data-src'))
return {
"title": title,
"time": time,
"tags": tags,
"description": description,
"image_url_list": image_url_list,
"website_url": website_url,
}
response.doc(‘body > article > section > article > header’)
参数和CSS的选择器类似,获取到 header 标签, .doc
函数返回的是一个 pyquery 对象。header(‘h1’).text()
通过参数 h1 获取到标签,text()
函数获取到标签中的文本内容,通过查看源码可知道,我们所需的标题数据为 h1 的文本。header.items(‘a’)
response.doc(‘div[id=”post_content”]’)
获取 id 值为 post_content 的 div 标签,并从中取得详情页的描述内容,有的页面这部分内容可能为空。具体html的源码如下图:
其余数据分析抓取的思路基本一致。
最终将需要的数据作为一个 dict 对象返回,即为最终的抓取结果
{
"title": title,
"time": time,
"tags": tags,
"description": description,
"image_url_list": image_url_list,
"website_url": website_url,
}
保存之后直接点击左边区域的 run 按钮运行起来,结果如图,中间灰色区域为分析抓取到的结果。
完整代码
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# Created on 2021-03-28 09:24:37
# Project: myReo
from pyspider.libs.base_handler import *
class Handler(BaseHandler):
crawl_config = {
}
@every(minutes=24 * 60)
def on_start(self):
self.crawl('http://reeoo.com/', callback=self.index_page)
@config(age=10 * 24 * 60 * 60)
def index_page(self, response):
for each in response.doc('div[class="thumb"]').items():
detail_url = each('a').attr.href
print(detail_url)
self.crawl(detail_url, callback=self.detail_page)
@config(priority=2)
def detail_page(self, response):
header = response.doc('body > article > section > article > header')
title = header('h1').text()
time = header('time').text()
tags = []
for each in header.items('a'):
tags.append(each.text())
content = response.doc('div[id="post_content"]')
description = content('blockquote > p').text()
website_url = content('a').attr.href
image_url_list = []
for each in content.items('img[data-src]'):
image_url_list.append(each.attr('data-src'))
return {
"title": title,
"time": time,
"tags": tags,
"description": description,
"image_url_list": image_url_list,
"website_url": website_url,
}
在主页把任务重新跑起来,查看运行结果
可以看到我们需要的数据都抓取下来
抓取到的数据默认存储到 resultdb 中,虽然很方便通过浏览器进行浏览和下载,但却不太适合进行大规模的数据存储。
所以最好的处理方式还是将数据保存在常用的数据库系统中,本例采用的数据库为 mongodb。
新建一个文件,命名为 config.json,放在 F:
盘下,以 JSON 格式存储配置信息。文件到时候作为 pyspider 配置命令的参数。
文件具体内容如下:
{
"taskdb": "mongodb+taskdb://127.0.0.1:27017/pyspider_taskdb",
"projectdb": "mongodb+projectdb://127.0.0.1:27017/pyspider_projectdb",
"resultdb": "mongodb+resultdb://127.0.0.1:27017/pyspider_resultdb",
"message_queue": "redis://127.0.0.1:6379/db"
}
Ps. 在运行之前,你得保证打开本地的数据库 mongodb 和 redis,如果pyspider缺失模块,安装即可。具体怎么玩自行搜索~
通过设置参数的命令重新运行起来:
pyspider -c f:config.json
通过重载 on_result(self, result) 函数,将结果保存到 mongodb 中,具体代码实现
def on_result(self, result):
if not result:
return
client = pymongo.MongoClient(host='127.0.0.1', port=27017)
db = client['pyspyspider_projectdb']
coll = db['website']
data = {
'title': result['title'],
'tags': result['tags'],
'time': result['time'],
'description': result['description'],
'website_url': result['website_url'],
'image_url_list': result['image_url_list']
}
data_id = coll.insert(data)
print (data_id)
on_result(self, result)
在每一步抓取中都会调用,但只在 detail_page 函数调用后参数中的 result 才会不为 None,所以需要在开始的地方加上判断。db = client[‘pyspyspider_projectdb’]
中数据库的名字 pyspyspider_projectdb 为之前在 config.json 配置文件中的值。coll = db[‘website’]
在数据库中创建了一张名为 website 的表。data_id = coll.insert(data)
将数据以我们制定的模式存储到 mongodb 中。
(除了重载on_result方法外,也可以通过重载ResultWorker类来实行结果的处理,需要在配置文件中加上对应的参数。)
重新新建一个任务,将完整的代码拷进去,在主界面完成的跑一遍。运行完成后,浏览器查看结果,因为设置了数据库的存储,不再存储在默认的 resultdb 中,此时浏览器的result界面是没有数据的
这里直接使用MongoDB安装完成后自带的MongoDBCompass查看数据
查看项目持久化
查看项目结果持久化