pyspider的架构主要分为Scheduler(调度器)、Fetcher(抓取器)、Processer(处理器)三个部分,整个爬取过程受到Monitor(监视器)的控制,抓取的结果被Result Worker(结果处理器)处理,如下图所示。
Scheduler发起任务调度,Fecher负责抓取网页内容,Processer负责解析网页内容,然后将新生成的Request发给Scheduler进行调度,将生成的提取结果输出保存。
1.打开cmd,输入命令
pyspider all
这样可以启动pyspider的所有组件,包括PhantomJS、ResultWorker、Processer、Fetcher、Scheduler、WebUI,这些都说pyspider运行必备的组件。可以打开浏览器,输入链接http://localhost:5000,这时我们会看到页面,如图所示:
此页面便是pyspider的WebUI,我们可以用它来管理项目、编写代码、在线调试、监控任务等。
新建一个项目,点击右边的create按钮,在弹出的浮窗里输入项目名称和爬取链接,在点击create按钮,这样就成功创建了一个项目,如图所示
接下来会看到pyspider的项目编辑和调试页面,如图所示;
左侧就是代码的调试页面,点击左侧右上角的run单步调试爬虫程序,在左侧下半部分可以预览当前的爬取页面。右侧是代码编辑页面,我们可以直接编辑代码和保存代码。
注意右侧,pyspider生成了一些代码,代码如下所示:
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# Created on 2018-09-22 16:46:39
# Project: q
from pyspider.libs.base_handler import *
class Handler(BaseHandler):
crawl_config = {
}
@every(minutes=24 * 60)
def on_start(self):
self.crawl('http://travel.qunar.com/travelbook/list.htm', 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(),
}
这样的Handler就是pyspider爬虫的主类,我们可以在此处定义爬取,解析、存储的逻辑。整个爬虫的功能只需要一个Handler即可完成。
接下来我们可以看到一个crawl_config属性。我们可以将本项目的所有爬取配置统一定义道这里,如定义Headers、设置代理等,配置之后全局生效。
然后,on_start()方法是爬取入口,初始的爬取请求会在这里产生,该方法通过调用crawl()方法即可新建一个爬取请求,第一个参数是爬取的URL,这里自动替换成我们定义的URL。crawl()方法还有一个参数callback,它指定了这个页面爬取成功后用哪个方法进行解析,代码中指定为index_page()方法,即如果这个URL对应的页面爬取成功了,那Response将交给index_page()方法进行解析。
index_page()方法恰好接收这个Response参数,Response对接了pyquery。我们直接调用doc()方法传入相应的CSS选择器,就可以像pyquery一样解析此页面,代码中默认是a[href^="http"],也就是说该方法解析了页面的所有链接,然后将链接遍历,再次调用了crawl()方法生成了新的爬取请求,同时指定了callback和detail_page,意识是说这些页面爬取成功了就调用detail_page()方法解析。这里,index_page()实现了两个功能,一是将爬取的结果进行解析,二是生成新的爬取请求。
detail_page()同样接收Response作为参数。detail_page()抓取的就是详情页的信息,就不会生成新的请求,只对Response对象做解析,解析之后将结果以字典的形式返回。当然我们也可以进行后续处理,如将数据保存到数据库中。
点击左栏右上角的run按钮,即可看到页面下方follows便会出现一个标注,其中包含数字1,这代表有新的爬取请求产生,如图所示。
左栏左上角会出现当前run的配置文件,这里有一个callback为on_start,这说明点击run之后实际是执行了on_start()方法。在on_start()方法中,我们利用crawl()方法生成一个爬取请求,那下方follows部分的数字1就代表了一个爬取请求。
点击下方的follows按钮,即可以看到生成的爬取请求链接。如图所示:
上方的callback已经变成了index_page,这就代表当前运行了index_page()方法。index_page()接收到的response参数就是刚才生成的第一个爬取请求的Response对象。index_page()方法通过调用doc()方法,传入提取所有a节点的CSS选择器,然后获取a节点的属性href,这样实际上就是获取第一个爬取页面中的所有链接。然后在index_page()方法里遍历了所有链接,同时调用了crawl()方法,就把这一个个的链接构造成新的爬取请求了。所以最下方follows按钮部分有231的数字标记,这就代表生成了217个爬取请求,同时这些请求的URL都呈现在当前页面了。
再点击下方的web按钮,即可预览当前爬取结果的页面,如图所示:
点击html按钮即可查看当前页面的源码,如图所示:
刚才在index_page()方法中提取了所以的链接并生成了新的爬取请求。
当我们要爬取攻略详情页面链接的时候我们就使用下方enable css selector helper这个工具即可,点击它,然后选择标题就会多一个红框,上方出现了一个CSS选择器,这就是当前标题对应的CSS选择器,如图所示:
在右侧代码选择要更改的区域,点击左栏的右箭头,此时在上方出现的标题的CSS选择器就会被替换到右侧代码中,如图所示:
这样就完成了CSS选择器的替换,非常便捷。
重新点击左栏右上角的run按钮,即可重新执行index_page()方法,此时的follows就变成了10个,也就是说现在我们提取的只有当前10个攻略,如图所示:
我们现在抓取的只是第一页的内容,还需要抓取后续页面,所以还需要一个爬取链接,即爬取下一页的攻略列表页面。我们再利用crawl()方法添加下一页的爬取请求,在index_page()方法里添加如下代码,然后点击save保存,如下图所示:
现在我们就把所以列表页的解析过程完成了。
任意选取一个详情页进入,点击前10个爬取请求中的任意一个的右箭头,执行详情页的爬取,如图所示
切换到Web页面预览效果,页面下拉之后,头图正文中的一些图片一直显示加载中
此现象的原因是pyspider默认发送HTTP请求,请求的HTML文档本身就不包含img节点。但是在浏览器中我们看到了图片,这时因为这张图片是后期经过JavaScript出现的。
我们可以将index_page()中生成抓取详情页的请求方法添加一个参数fetch_type,改写的index_page()变为如下内容:
for each in response.doc('li > .tit > a').items():
self.crawl(each.attr.href, callback=self.detail_page,fetch_type='js')
next=response.doc('.next').attr.href
self.crawl(next,callback=self.index_page)
接下来看效果:
图片被成功渲染出来了,这就是启动了PhantomJS渲染后的结果。只需要添加一个fetch_type参数即可,这非常的方便。
最好在detail_page()方法改写如下所示:
def detail_page(self, response):
return {
"url": response.url,
"title": response.doc('#booktitle').text(),
"date":response.doc('.when .data').text(),
"day":response.doc('.howlong .data').text(),
"who":response.doc('.who .data').text(),
"text":response.doc('#b_panel_schedule').text(),
"image":response.doc('.cover_img').attr.src
}
我们分别提取了页面的链接、标题、出行日期、出行天数、人物、攻略正文、头图信息、将这些信息构造成一个字典。
返回爬虫的主页面,将爬虫的status设置成DEBUG或RUNNING,点击右侧的Run按钮即可开始爬取,如图所示
在最左侧我们可以定义项目的分组,以方便管理。rate/burst代表当前的爬取速率,rate代表1秒发出多少个请求,burst相当于流量控制中的令牌桶算法的令牌数,rate和burst设置的越大,爬取速率越快,当然速率需要考虑本机性能和爬取过快被封的问题。process中的5m、1h、1d指的是最近5分、1小时、1天内的请求情况,all代表所有的请求情况。请求由不同颜色表示,蓝色代表等待被执行的请求,绿色的代表成功的请求,黄色的代表请求失败后等待重试的请求,红色的代表失败次数过多而被忽略的请求,这样可以直观知道爬取的进度和请求情况,如图所示
点击Active Tasks,即可查看最近请求的详细情况,如图所示:
点击Results,即可查看所有爬取的结果,如图所示:
点击右上角的按钮,即可获得数据的JSON、CSV格式。
self.crawl
是告诉pyspider应该抓取哪个url的主界面。
url是爬取时的URL,可以定义为单个URL字符串,也可以定义成URL列表。
callback是回调函数,指定了该URL对应的响应内容用哪个方法来解析,如下所示:
def on_start(self):
self.crawl('http://travel.qunar.com/travelbook/list.htm', callback=self.index_page)
这里指定了callback为index_page,就代表爬取http://travel.qunar.com/travelbook/list.htm'链接得到的响应会用index_page()方法来解析。
age是任务的有效时间。如果某个任务在有效时间内且已经被执行,则它不会重复执行,如下所示:
@config(age=10 * 24 * 60 * 60)
def index_page(self, response):
默认的有效时间为10天。
priority是爬取任务的优先级,其默认值是0,priority的数值越大,对应的请求会越优先被调度,如下所示:
def index_page(self):
self.crawl('http://www.example.org/page2.html', callback=self.index_page)
self.crawl('http://www.example.org/233.html', callback=self.detail_page,priority=1)
第二个任务会优先被调用,233.html这个链接优先爬取。
exetime参数可以设置定时任务,其值是时间戳,默认是0,即代表立即执行,如下所示:
import time def on_start(self):
self.crawl('http://www.example.org/', callback=self.callback,exetime=time.time()+30*60)
这样该任务会在30分钟之后执行。
retries可以定义重试次数,其默认值是3.
itag参数设置判定网页是否发生变化的节点值,在爬取时会判定次当前节点是否和上次爬取到的节点相同。如果节点相同,则证明页面没有更新,就不会重复爬取,如下所示:
def index_page(self, response):
for item in response.doc('.item').items():
self.crawl(item.find('a').attr.url, callback=self.detail_page, itag=item.find('.update-time').text())
class Handler(BaseHandler):
crawl_config = { 'itag': 'v223' }
修改全局参数itag,使所有任务都重新执行(需要点run按钮来启动任务)
当开启时,爬取任务在过期后会重新执行,循环时间即定义的age时间长度,如下所示"
def on_start(self):
self.crawl('http://www.example.org/', callback=self.callback,age=5*60*60, auto_recrawl=True)
这里定义了age有效期为5个小时,设置了auto_recrawl为True,这样任务就会每5个小时执行一次。
method是HTTP的请求方式,它默认是GET。如果想发起POST请求,可以将method设置为POST。
我们可以方便地使用params来定义GET请求参数,如下所示:
def on_start(self):
self.crawl('http://httpbin.org/get', callback=self.callback,params={'a': 123, 'b': 'c'})
self.crawl('http://httpbin.org/get?a=123&b=c', callback=self.callback)
这里两个爬取任务是等价的
data是POST表单数据。当请求方式为POST时,我们可以通过此参数传递表单数据,如下所示:
def on_start(self):
self.crawl('http://httpbin.org/post', callback=self.callback,method='POST', data={'a': 123, 'b': 'c'})
files是上传的文件,需要指定文件名,如下所示:
def on_start(self):
self.crawl('http://httpbin.org/post', callback=self.callback,method='POST', files={field:{filename:'content'}})
user_agent是爬取使用的User_Agent.
headers是爬取时使用的Headers,即Request Headers。
cookies是爬取时使用的Cookies,为字典格式。
connect_timeout是在初始化连接时的最长等待时间,它默认是20秒。
timeout是爬取网页时的最长等待时间,它默认是120秒。
确定是否自动处理重定向,它默认为True。
确定是否验证证书,此选项对HTTPS请求有效,默认是True。
proxy是爬取时使用的代理,它支持用户名密码的配置,格式是username:password@hostname:port,如下所示:
class Handler(BaseHandler):
crawl_config = { 'proxy': 'localhost:8080' }
fetch_type开启PhantomJS渲染。如果遇到JavaScript渲染的页面,指定字段即可实现PhantomJS的对接,pyspider将会使用PhantomJS进行网页的抓取,如下所示:
def on_start(self):
self.crawl('http://www.taobao.com', callback=self.callback,method='POST', fetch_type='js')
这样我们就可以实现淘宝页面的抓取了,得到的结果就是浏览器中看到的效果。
js_script是页面加载完毕后执行的Javascript脚本,如下所示:
def on_start(self):
self.crawl('http://www.example.org/', callback=self.callback,fetch_type='js', js_script='''
function() {
window.scrollTo(0,document.body.scrollHeight);
return 123;
}
''')
页面加载成功后将执行页面混动的JavaScript代码,页面会下拉到最底部。
在加载JavaScript页面时确定是否加载图片,它默认是否
save参数非常有用,可以在不同的方法之间传递参数,如下所示:
def on_start(self):
self.crawl('http://www.example.org/', callback=self.callback,save={'page': 123}) def callback(self, response): return response.save['page']
在on_start()方法中生成Request并传递额外的参数page,在回调函数里可以通过response遍历的save字段接收到这些参数值
取消任务,如果一个任务是ACTIVE状态的,则需要force_update设置为True。
即使任务处于ACTIVE状态,也会强制更新状态。
每个项目都有6个状态,分别是TODO、STOP、CHECKING、DEBUG、RUNNING、PAUSE。
TODO:项目刚刚被创建还未实现时的状态。
STOP:如果想停止某项目的抓取,可以将项目的状态设置成STOP
CHECKING:正在运行的项目被修改后就会变成CHECKING状态,项目在中途出错需要调整的时候会遇到这种情况。
DEBUG/RUNNING:这两个状态对项目的运行没有影响,状态设置成任意一个,项目都可以运行,但是可以用二者来区分项目是否已经测试通过。
PAUSE:当爬取过程中出现连续多次错误时,项目会自动设置为PAUSE状态,并等待一定时间后继续抓取。