爬虫框架
Scrapy
安装
anaconda安装
如果已经安装好了 Anaconda,那么可以通过 conda 命令安装 Scrapy,安装命令如下:
conda install Scrapy
运行之后便可以完成 Scrapy 的安装。
其他安装
pip install scrapy
验证
用途
适用于抓取大量数据的时候。
文档
框架介绍
架构
scrapy架构可分为如下部分:
- Engine 引擎,处理整个系统的数据流处理、触发事务,是整个框架的核心
- Item 项目,它定义了爬取结果的数据结构,爬取的数据会被赋值成该 Item 对象
- Scheduler 调度器,接受引擎发过来的请求并将其放在队列中,在引擎再次请求的时候将请求提供给引擎
- Downloader 下载器,下载网页内容,并将网页内容返回给蜘蛛
- Spiders 蜘蛛,其内定义了爬取的逻辑和网页 解析规 ,它主要负责解析响应并生成提取结果和新的请求
- Item Pipeline 项目管道,负责处理由蜘蛛从网页中 取的项目,它的主要任务是清洗、验证和存储数据
- Downloader Middlewares 下载器中间件,位于引擎和下载器之 的钩子框架,主要处理引擎与下载器之间的请求及响应
- Spide Middlewares 蜘蛛中间件,位于引擎和蜘蛛之 的钩子框架,主要处理蜘蛛输入的响应和输出的结果及新的请求
数据流
Scrapy 中的数据流由引擎控制,数据流的过程如下
- Engine 引擎,处理整个系统的数据流处理、触发事务,是整个框架的核心
- Item 项目,它定义了爬取结果的数据结构,爬取的数据会被赋值成该 Item 对象
- Scheduler 调度器,接受引擎发过来的请求并将其 列中 在引擎再次请求的时候将请求提供给引擎
- Downloader 下载器,下载网页内容,并将网页 容返回给蜘蛛
- Spiders 蜘蛛,其内定义了爬取的逻辑和网页 解析规 ,它主要负责解析响应并生成提取结果和新的请求
- Item Pipeline 项目管道,负责处理由蜘蛛从网页中获取的项目,它的主要任务是清洗、验证和存储数据
- Downloader Middlewares 下载器中间件,位于引擎和下载器之 的钩子框架,主要处理引擎与下载器之间的请求及响应
- Spide Middlewares 蜘蛛中间件,位于引擎和蜘蛛之 的钩子框架,主要处理蜘蛛输入的响应和输出的结果及新的请求
项目结构
Scrapy 框架和pyspider不同,它是通过命令行来 建项目的,代码 编写还是需要 IDE, 项目
建之后,项目文件结构如下所示
- scrapy.cfg
- project
- init.py
- items.py
- pipelines.py
- settings.py
- middlewares.py
- spiders/
- init .py
- spider1.py
- spider2.py
- project
这里各个文件的功能描述如下:
- scrapy.cfg: 它是Scrapy项目的配置文件,其内定义了项目的配置文件路径 部署相关信息等内容
- items.py :它定义Item 数据结构,所有的 Item 的定义都可以放这里
- pipelines.py: 它定义Item Pipeline 的实现,所有的 Item Pipeline 实现都可以放这里
- settings.py: 它定义项目的全局配置
- middlewares.py: 它定义 Spider Middlewares Downloader Middlewares
- spiders: 其内包含 Spider 的实现,每个 Spider 都有一个文件
入门
创建项目
创建 Scrapy 项目,项目文件可以直接用 scrapy 命令生成
scrapy startproject myproject
文件夹结构如下所示:
scrapy. cfg # Scrapy 部署时的配置文件
myproject #项目的模块 需要从这里引入
_init_.py
items.py # Items 的定义,定义爬取的数据结构
middlewares.py # Middlewares 的定义,定义爬取时的中间件
pipelines.py # Pipelines 的定义,定义数据管道
settings.py #配置文件
spiders #放置 Spider 的文件夹
_init_ py
创建spider
Spider 是自定义的类, Scrapy 用它来从网页里抓取内容,并解析抓取的结果 不过这个类必须继承 Scrapy 提供的 Spider类 scrapy.Spider ,还要定义 Spider 的名称和起始请求,以及怎样处理爬取后的结果的方法。
使用命令行创建一个 Spider:
cd myproject
scrapy genspider tff http://thefoxfairy.gitbook.io/
进入刚才创建的 myproject文件夹,然后执行 genspider 命令 第一个参数是 Spider 名称,第二个参数是网站域名 执行完毕之后, spiders 文件夹中多了一个 tff.py ,就是刚刚创建的 Spider。
# -*- coding: utf-8 -*-
import scrapy
class TffSpider(scrapy.Spider):
name = 'tff'
allowed_domains = ['thefoxfairy.gitbook.io']
start_urls = ['http://thefoxfairy.gitbook.io']
def parse(self, response):
pass
这里有四个属性:name、allowed_domains 和 start_urls
,还有一个方法 parse
。
name
:是每个项目唯一的名字,用来区分不同的 Spider。allowe_domains
:是允许爬取的域名,如果初始或后续的请求链接不是这个域名下的,则请求链接会被过滤掉。start_urls
:包含了 Spider 在启动时爬取的 url 列表,初始请求是由它来定义的。parse
:是 Spider 一个方法 默认情况下,被调用时 start_urls 里面的链接构成的请求完成下载执行后,返回的响应就会作为唯一的参数传递给这个函数
,该方法负责解析返回的响应、提取数据或者进一步生成要处理的请求。
定义item
Item 是保存爬取数据的容器,使用方法和字典类似 不过,相比字典, Item 多了额外的保护机制,可以避免拼写错误或者定义字段错误。
创建 Item 需要继承 scrapy.Item 类,并且定义类型为 scrapy.Field 字段。
import scrapy
class BaiduItem(scrapy.Item):
title = scrapy.Field()
解析Response
parse()方法的参数resposne是start_urls 里面的链接爬取后的结果,所以在parse()方法中,可以直接对 response 变量包含的内容进行解析,比如浏览请求结果的网页源代码,或者进一步分析源代码内容,或者找出结果中的链接而得到下一个请求。
提取的方式能够采用 css 选择器或 XPath 选择器。
class TffSpider(scrapy.Spider):
name = 'tff'
allowed_domains = ['thefoxfairy.gitbook.io']
start_urls = ['http://thefoxfairy.gitbook.io']
def parse(self, response):
title = response.css("title::text").extract_first()
print("*"*100)
return title
::text
:获取节点的正文内容extract_ first()
:获取第一个元素extract()
:获取所有元素,返回列表
使用item
Item可以理解为一个字典,不过在声明的时候需要实例,然后依次用刚才解析的结果赋值Item的每一个字 段, 后将Item返回即可。
from ..items import TffItem
class TffSpider(scrapy.Spider):
name = 'tff'
allowed_domains = ['thefoxfairy.gitbook.io']
start_urls = ['http://thefoxfairy.gitbook.io']
def parse(self, response):
item = TffItem()
item["title"] = response.css("title::text").extract_first()
print("*"*100)
return item
运行爬虫
格式:
scrapy crawl 爬虫名称
如下:
scrapy crawl tff
scrapy终端(scrapy shell)
scrapy shell能够很方便的进行调试代码,可以预先通过scrapy shell根据选择器来获取数据。
格式:
scrapy shell <url>
<url>
是要爬取的网页的地址
如下:
scrapy shell https://thefoxfairy.gitbook.io/
selectors选择器
这里选择采用xpath
xpath用法
表达式 | 描述 |
---|---|
nodename | 选取此节点的所有子节点 |
/ | 从当前节点选取直接子节点 |
// | 从当前节点选取子孙节点 |
. | 选取当前节点 |
.. | 选取当前节点的父节点 |
@ | 选取属性 |
* | 通配符,选择所有元素节点与元素名 |
@* | 选取所有属性 |
[@attrib] | 选取具有给定属性的所有元素 |
[@attrib='value] | 选取给定属性具有给定值的所有元素 |
[tag] | 选取所有具有指定元素的直接子节点 |
[tag="text"] | 选取所有具有指定元素并且文本内容是text节点 |
构造选择器
使用python shell交互模式。
导入模块
>>> from scrapy.selector import Selector
>>> from scrapy.http import HtmlResponse
构造数据
- 以字符串构造
>>> body = '<html><body><span>good</span></body></html>'
>>> Selector(text=body).xpath('//span/text()').extract()
['good']
- 以reponse构造
>>> response = HtmlResponse(url='http://example.com', body=body,encoding="utf-8")
>>> Selector(response=response).xpath('//span/text()').extract()
['good']
可以通过response对象以.selector
属性方法,达到同样效果
>>> response.selector.xpath('//span/text()').extract()
['good']
使用选择器
打开shell
scrapy shell http://doc.scrapy.org/en/latest/_static/selectors-sample1.html
网站源码
<html>
<head>
<base href='http://example.com/' />
<title>Example website</title>
</head>
<body>
<div id='images'>
<a href='image1.html'>Name: My image 1 <br /><img src='image1_thumb.jpg' /></a>
<a href='image2.html'>Name: My image 2 <br /><img src='image2_thumb.jpg' /></a>
<a href='image3.html'>Name: My image 3 <br /><img src='image3_thumb.jpg' /></a>
<a href='image4.html'>Name: My image 4 <br /><img src='image4_thumb.jpg' /></a>
<a href='image5.html'>Name: My image 5 <br /><img src='image5_thumb.jpg' /></a>
</div>
</body>
</html>
提取数据
- 提取所有数据
获取属性id下的所有a标签的文本内容
>>> response.xpath('//div[@id="images"]/a/text()').extract()
['Name: My image 1 ',
'Name: My image 2 ',
'Name: My image 3 ',
'Name: My image 4 ',
'Name: My image 5 ']
- 提取第一个匹配的数据
获取属性id下的第一个a标签的文本内容
>>> response.xpath('//div[@id="images"]/a/text()').extract_first()
'Name: My image 1 '
- 获取属性值
获取a标签的属性href的值
>>> response.xpath('//div[@id="images"]//@href').extract()
['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html']
- 匹配内容
含有属性值href中值有"image"的所有标签下的href属性值
>>> response.xpath('//a[contains(@href, "image")]/@href').extract()
['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html']
嵌套选择器
选择器方法( .xpath()
)返回相同类型的选择器列表,因此可以对这些选择器调用选择器方法。
>>> links = response.xpath('//a[contains(@href, "image")]')
>>> links.extract()
[u'<a href="image1.html">Name: My image 1 <br><img src="image1_thumb.jpg"></a>',
u'<a href="image2.html">Name: My image 2 <br><img src="image2_thumb.jpg"></a>',
u'<a href="image3.html">Name: My image 3 <br><img src="image3_thumb.jpg"></a>',
u'<a href="image4.html">Name: My image 4 <br><img src="image4_thumb.jpg"></a>',
u'<a href="image5.html">Name: My image 5 <br><img src="image5_thumb.jpg"></a>']
>>> for index, link in enumerate(links):
args = (index, link.xpath('@href').extract(), link.xpath('img/@src').extract())
print 'Link number %d points to url %s and image %s' % args
Link number 0 points to url [u'image1.html'] and image [u'image1_thumb.jpg']
Link number 1 points to url [u'image2.html'] and image [u'image2_thumb.jpg']
Link number 2 points to url [u'image3.html'] and image [u'image3_thumb.jpg']
Link number 3 points to url [u'image4.html'] and image [u'image4_thumb.jpg']
Link number 4 points to url [u'image5.html'] and image [u'image5_thumb.jpg']
结合正则表达式使用选择器(selectors)
Selector
也有一个 .re()
方法,用来通过正则表达式来提取数据。然而,不同于使用 .xpath()
或者 .css()
方法, .re()
方法返回unicode字符串的列表。所以无法构造嵌套式的 .re()
调用。
>>> response.xpath('//a[contains(@href, "image")]/text()').re("Name:\s(.*)\s")
['My image 1', 'My image 2', 'My image 3', 'My image 4', 'My image 5']
Spiders用法
介绍
Spider类定义了如何爬取某个(或某些)网站。包括了爬取的动作以及如何从网页的内容中提取结构化数据(爬取item)。
爬取步骤:
- 以初始的URL初始化Request,并设置回调函数。 当该request下载完毕并返回时,将生response,并作为参数传给该回调函数。
- spider中初始的request是通过调用
start_requests()
来获取的。start_requests()
读取start_urls
中的URL, 并以parse
为回调函数生成Request
。 - 在回调函数内分析返回的(网页)内容,返回
Item
对象、dict
、Request
或者一个包括三者的可迭代容器。 返回的Request对象之后会经过Scrapy处理,下载相应的内容,并调用设置的callback函数(函数可相同)。 在回调函数内,可以使用 选择器(Selectors) (也可以使用BeautifulSoup, pyquery或者想用的任何解析器) 来分析网页内容,并根据分析的数据生成item。 - 最后,由spider返回的item将被存到数据库(由某些 Item Pipeline 处理)或使用 Feed exports 存入到文件中。
scrapy.Spider
Spider是最简单的spider。每个其他的spider必须继承自该类(包括Scrapy自带的其他spider以及自己编写的spider)。 Spider并没有提供什么特殊的功能。 其仅仅提供了 start_requests()
的默认实现,读取并请求spider属性中的 start_urls
, 并根据返回的结果(resulting responses)调用spider的 parse
方法。
Splash
安装
- docker已安装
- 安装Splash
docker pull scrapinghub/splash
ScrapySplash 会使用 Splash 的 HTTP API 进行页面渲染,所以需要安装 Splash 来提供渲染服务,安装是通过Docker进行安装,在这之前请确保已经正确安装好了 Docker。
安装命令如下:
docker run -p 8050:8050 scrapinghub/splash
安装完成之后会有类似的输出结果:
2020-09-18 14:53:05+0000 [-] Log opened.
2020-09-18 14:53:05.621345 [-] Xvfb is started: ['Xvfb', ':363443614', '-screen', '0', '1024x768x24', '-nolisten', 'tcp']
QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '/tmp/runtime-splash'
2020-09-18 14:53:06.495091 [-] Splash version: 3.5
2020-09-18 14:53:06.585932 [-] Qt 5.14.1, PyQt 5.14.2, WebKit 602.1, Chromium 77.0.3865.129, sip 4.19.22, Twisted 19.7.0, Lua 5.2
2020-09-18 14:53:06.586182 [-] Python 3.6.9 (default, Jul 17 2020, 12:50:27) [GCC 8.4.0]
2020-09-18 14:53:06.586284 [-] Open files limit: 1048576
2020-09-18 14:53:06.586339 [-] Can't bump open files limit
2020-09-18 14:53:06.629053 [-] proxy profiles support is enabled, proxy profiles path: /etc/splash/proxy-profiles
2020-09-18 14:53:06.629299 [-] memory cache: enabled, private mode: enabled, js cross-domain access: disabled
2020-09-18 14:53:06.836124 [-] verbosity=1, slots=20, argument_cache_max_entries=500, max-timeout=90.0
2020-09-18 14:53:06.836423 [-] Web UI: enabled, Lua: enabled (sandbox: enabled), Webkit: enabled, Chromium: enabled
2020-09-18 14:53:06.836844 [-] Site starting on 8050
2020-09-18 14:53:06.836932 [-] Starting factory <twisted.web.server.Site object at 0x7f75940815c0>
2020-09-18 14:53:06.837234 [-] Server listening on http://0.0.0.0:8050
这样就证明 Splash 已经在 8050 端口上运行了。
这时打开:http://localhost:8050即可看到 Splash 的主页
当然 Splash 也可以直接安装在远程服务器上,在服务器上运行以守护态运行 Splash 即可,命令如下:
docker run -d -p 8050:8050 scrapinghub/splash
在这里多了一个 -d 参数,它代表将 Docker 容器以守护态运行,这样在中断远程服务器连接后不会终止 Splash 服务的运行。
用途
一个页面渲染服务器,返回渲染后的页面,便于爬取,便于规模应用。
文档
用法
实例
脚本语言内容:
function main(splash, args)
assert(splash:go(args.url))
assert(splash:wait(0.5))
return {
html = splash:html(),
png = splash:png(),
har = splash:har(),
}
end
- wait():等待
- html:返回页面的源码
- png:返回页面的截图
- HAR:返回页面的HAR信息
Splash Lua脚本
入口及返回值
function main(splash, args)
assert(splash:go("https://www.baidu.com"))
assert(splash:wait(0.5))
local title = splash:evaljs("document.title")
return {
title = title
}
end
通过spalsh:evaljs()方法传入javascript脚本。Splash默认调用main()方法方法的返回值为字典形式或者是字符串形式,最后都会转化为一个Splash HTTP Response
function main(splash, args)
return {
hello = "world"
}
end
异步处理
function main(splash, args)
local example_urls = {"www.baidu.com", "www.taobao.com", "www.zhihu.com"}
local urls = args.urls or example_urls
local results = {}
for index, url in ipairs(urls) do
local ok, reason = splash:go("http://" .. url)
if ok then
splash:wait(2)
results[url] = splash:png()
end
end
return results
end
wait()
:等待的描述..
:字符串拼接操作符ipairs()
:操作字典进行迭代
Splash对象属性
args
splash对象的args属性可以获取加载时配置的参数
function main(spalsh,args)
local url = args.url
return url
end
运行结果:
Splash Response: "https://www.qidian.com/"
js_endabled
js_endabled属性是splash的javascript执行开关,可以将其配置为True或False来控制是否可以执行JavaScript代码,默认为True
function main(splash,args)
splash:go(args.url)
splash.js_enabled = false
local title = splash:evaljs("document.title")
return {
title = title,
}
end
禁用之后,调用evaljs()方法执行javascript代码,会抛出异常
{
"description": "Error happened while executing Lua script",
"info": {
"message": "[string \"function main(splash,args)\r...\"]:4: unknown JS error: None",
"line_number": 4,
"source": "[string \"function main(splash,args)\r...\"]",
"splash_method": "evaljs",
"type": "JS_ERROR",
"error": "unknown JS error: None",
"js_error_message": null
},
"error": 400,
"type": "ScriptError"
}
一般情况下默认开启
resoure_timeout
设置加载的超时时间,单位为秒数。如果设置为0或nil就表示不检测超时
function main(splash,args)
splash.resource_timeout = 0.1
splash:go(args.url)
return splash:png()
end
设置超时为0.1秒,如果在0.1秒之内没有得到响应就会抛出异常
images_enabled
设置图片是否加载
function main(splash,args)
splash.images_enabled = false
splash:go(args.url)
return splash:png()
end
plugins_enabled
控制浏览器插件是否开启,如Flash。默认情况下事是False/不开启
splash.plugins_enabled = flase/true
scroll_position
控制页面的滚动偏移
splash.scroll_position = {x=x,y=y}
function main(splash,args)
splash.images_enabled = true
splash:go(args.url)
splash:wait(3)
splash.scroll_position = {y=200}
return splash:png()
end
Splash对象方法
go()
go():用来请求某个链接的方法,可以模拟GET和POST请求,同时支持传入Headers、From Data等数据
ok, reason = splash:go{url, baseurl=nil, headers=nil, http_method="GET", body=nil, formdata=nil}
参数说明:
- url:请求的url
- baseurl:资源加载相对路径
- headers:请求的headers
- http_method:默认是get,同时支持post
- body:post的时候的表单数据,使用的Content-type为application
- fromdata:post的时候表单数据,使用的Content-type为application/x-www-form-urlencoded
返回的结果是结果ok和原因reason的组合,如果ok为空,代表网页加载出现了错误,此时reason变量中包含了错误的原因,否则证明页面加载成功
function main(splash,args)
local ok,reason = splash:go{"http://httpbin.org/post",http_method="POST",body="name-Germey"}
if ok then
return splash:html()
end
end
模拟post请求,并传入POST的表单数据,如果成功,则返回用页面源代码
运行结果:
<html><head></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">{
"args": {},
"data": "",
"files": {},
"form": {
"name-Germey": ""
},
"headers": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "en,*",
"Connection": "close",
"Content-Length": "11",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"Origin": "null",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/602.1 (KHTML, like Gecko) splash Version/9.0 Safari/602.1"
},
"json": null,
"origin": "58.16.27.216",
"url": "http://httpbin.org/post"
}
</pre></body></html>
wait()
控制页面等待时间
ok,reason = splash:wait(time,cancel_on_redirect=false,cancle_on_error=True)
参数说明:
- time:等待秒数
- cancel_on_redirect:默认Fasle,如果发生了重定向就停止等待,并返回重定向结果
- cancel_on_error:默认False,如果加载发生了加载错误就停止等待
function main(splash,args)
splash:go(args.url)
splash:wait(2)
return splash:html()
end
jsfunc()
直接调用javascript定义的方法,需要用双中括号包围,相等于实现了javascript方法到lua脚本的转换
function main(splash, args)
local get_div_count = splash:jsfunc([[
function () {
var body = document.body;
var divs = body.getElementsByTagName('div');
return divs.length;
}
]])
splash:go("https://www.baidu.com")
return ("There are %s DIVs"):format(
get_div_count())
end
运行结果:
Splash Response: "There are 23 DIVs"
evaljs()
执行javascript代码并返回最后一条语句的返回结果
result = splash:evaljs(js)
runjs()
执行JavaScript代码类似于evaljs()功能类似,但偏向于执行某些动作或声明某些方法evaljs()偏向于获取某些执行结果
function main(splash,args)
splash:go("https;//www.baidu.com")
splash:runjs("foo = function(){return 'bar'}")
local result = splash:evaljs("foo()")
return result
end
运行结果:
Splash Response: "bar"
autoload()
设置每个页面访问时自动加载的对象
ok,reason = splash:autoload{source_or_url,source=nil,url=nil}
参数说明:
- source_or_url:JavaScript代码或者JavaScript库链接
- source,JavaScript代码
- url,JavaScript库链接
只负责加载JavaScript代码或库,不执行任何操作,如果要执行操作可以调用evaljs()或runjs()方法
function main(splash,args)
splash:autoload([[
function get_documetn_title(){
return document.title;
}
]])
splash:go("https://www.baidu.com")
return splash:evaljs("get_documetn_title()")
end
运行结果:
Splash Response: "百度一下,你就知道"
加载某些方法库,如JQuery
function main(splash,args)
splash:autoload("https://code.jquery.com/jquery-2.1.3.min.js")
splash:go("https://www.baidu.com")
local version = splash:evaljs("$.fn.jquery")
return "JQuery version: " .. version
end
运行结果:
Splash Response: "JQuery version: 1.10.2"
call_later()
可以通过设置定时任务和延迟时间实现任务延时执行,并且可以在执行前通过 cancel() 方法重新执行定时任务
function main(splash,args)
local snapshots = {}
local timer = splash:call_later(function()
snapshots['a'] = splash:png()
splash:wait(1.0)
snapshots["b"] = splash:png()
end,0.2)
splash:go("https://www.taobao.com")
splash:wait(3.0)
return snapshots
end
http_get()
此方法可以模拟发送 HTTP 的 GET 请求,使用方法如下:
response = splash:http_get{url, headers=nil, follow_redirects=true}
参数说明如下:
- url,请求URL。
- headers,可选参数,默认为空,请求的 Headers。
- follow_redirects,可选参数,默认为 True,是否启动自动重定向。
http_post()
和 http_get() 方法类似,此方法是模拟发送一个 POST 请求,不过多了一个参数 body,使用方法如下
response = splash:http_post{url, headers=nil, follow_redirects=true, body=nil}
参数说明如下:
- url,请求URL。
- headers,可选参数,默认为空,请求的 Headers。
- follow_redirects,可选参数,默认为 True,是否启动自动重定向。
- body,可选参数,默认为空,即表单数据。
function main(splash, args)
local treat = require("treat")
local json = require("json")
local response = splash:http_post{"http://httpbin.org/post",
body=json.encode({name="Germey"}),
headers={["content-type"]="application/json"}
}
return {
html=treat.as_string(response.body),
url=response.url,
status=response.status
}
end
set_content()
此方法可以用来设置页面的内容
function main(splash)
assert(splash:set_content("<html><body><h1>hello Angle</h1></body></html>"))
return splash:png()
end
html()
此方法可以用来获取网页的源代码
png()
此方法可以用来获取 PNG 格式的网页截图
jpeg()
此方法可以用来获取 JPEG 格式的网页截图
har()
此方法可以用来获取页面加载过程描述
url()
此方法可以获取当前正在访问的 URL
get_cookies()
此方法可以获取当前页面的 Cookies
add_cookie()
此方法可以为当前页面添加 Cookie
cookies = splash:add_cookie{name, value, path=nil, domain=nil, expires=nil, httpOnly=nil, secure=nil}
function main(splash)
splash:add_cookie{"sessionid", "asdadasd", "/", domain="http://example.com"}
splash:go("http://example.com/")
return splash:html()
end
clear_cookies()
此方法可以清除所有的 Cookies
splash:clear_cookies()
get_viewport_size()
此方法可以获取当前浏览器页面的大小,即宽高
set_viewport_size()
此方法可以设置当前浏览器页面的大小,即宽高
spalsh:set_viewport_size(x,y)
set_viewport_full()
此方法可以设置浏览器全屏显示
set_user_agent()
此方法可以设置浏览器的 User-Agent
splash:set_user_agent("Splash")
set_custom_headers()
此方法可以设置请求的 Headers
set_custom_headers()
此方法可以设置请求的 Headers
select()
select() 方法可以选中符合条件的第一个节点,如果有多个节点符合条件,则只会返回一个,其参数是 CSS 选择器
function main(splash,args)
splash:go(args.url)
input = splash:select("#kw")
input:send_text('Splash')
splash:wait(3)
return splash:png()
end
select_all()
此方法可以选中所有的符合条件的节点,其参数是 CSS 选择器
function main(splash)
local treat = require('treat')
assert(splash:go("http://quotes.toscrape.com/"))
assert(splash:wait(0.5))
local texts = splash:select_all('.quote .text')
local results = {}
for index, text in ipairs(texts) do
results[index] = text.node.innerHTML
end
return treat.as_array(results)
end
mouse_click()
此方法可以模拟鼠标点击操作,传入的参数为坐标值 x、y,也可以直接选中某个节点直接调用此方法
function main(splash,args)
splash:go(args.url)
input = splash:select("#kw")
input:send_text('Splash')
submit = splash:select("#su")
submit:mouse_click()
splash:wait(3)
return splash:png()
end
Splash API调用
render.html
用于获取javascript渲染页面的HTML代码
curl http://localhost:8050/render.html?url=https://www.baidu.com
import requests
url = "http://192.168.99.100:8050/render.html?url=https://www.baidu.com&wait=5"
response = requests.get(url)
print(response.text)
render.png
获取网页截图
参数:
- height:高
- width:宽
curl http://localhost:8050/render.png?url=https://www.taobao.com&wait=5&width=1000&height=700
import requests
url = "http://192.168.99.100:8050/render.png?url=https://www.taobao.com&wait=10&width=1000&height=700"response = requests.get(url)with open('taobao.png','wb') as f: f.write(response.content)
图片:https://splash.readthedocs.io/en/stable/api.html#render-png
render.har
此接口用于获取页面加载的 HAR 数据
curl http://localhost:8050/render.har?url=https://www.jd.com&wait=5
返回结果非常多,是一个 Json 格式的数据,里面包含了页面加载过程中的 HAR 数据。
render.json
此接口包含了前面接口的所有功能,返回结果是 Json 格式
curl http://localhost:8050/render.json?url=https://httpbin.org
更多参数:https://splash.readthedocs.io/en/stable/api.html#render-json
execute
此接口可以实现和 Lua 脚本的对接
function main(splash)
return "hello"
end
curl http://localhost:8050/execute?lua_source=function+main%28splash%29%0D%0A++return+%27hello%27%0D%0Aend
import requestsfrom urllib.parse import quote
lua = '''function main(splash) return 'hello'end'''
url = "http://192.168.99.100:8050/execute?lua_source=" + quote(lua)response = requests.get(url)print(response.text)
import requestsfrom urllib.parse import quote
lua = '''function main(splash, args) local treat = require("treat") local response = splash:http_get("http://httpbin.org/get") return { html=treat.as_string(response.body), url=response.url, status=response.status }end'''
url = 'http://localhost:8050/execute?lua_source=' + quote(lua)response = requests.get(url)print(response.text)
Pyspider
安装
创建虚拟环境python版本为python3.6
conda create -n python36 python=3.6
激活虚拟环境
activate pys
删除虚拟环境
conda remove -n python36 --all
查看已有虚拟环境
conda env list
Windows 下可能会出现这样的错误提示:
Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-build-vXo1W3/pycurl
这是pycurl安装错误,需要安装pucurl下载地址,找到与之对应的wheel文件。
如Windows 64 位,Python3.6 则下载 pycurl‑7.43.0‑cp36‑cp36m‑win_amd64.whl
pip install pycurl‑7.43.0‑cp36‑cp36m‑win_amd64.whl
Linux 下如果遇到 PyCurl 的错误:
__main__.ConfigurationError: Could not run curl-config: [Errno2] No such file or directory
解决方案:
sudo apt-get install libcurl4-gnutls-dev
运行安装后即可正常安装pycurl
。
验证,启动pyspider
pyspider all
控制平台如下输出:
D:\>pyspider all
Traceback (most recent call last):
File "d:\programdata\anaconda3\lib\runpy.py", line 193, in _run_module_as_main
"__main__", mod_spec)
File "d:\programdata\anaconda3\lib\runpy.py", line 85, in _run_code
exec(code, run_globals)
File "D:\ProgramData\Anaconda3\Scripts\pyspider.exe\__main__.py", line 4, in <module>
File "d:\programdata\anaconda3\lib\site-packages\pyspider\run.py", line 231
async=True, get_object=False, no_input=False):
^
SyntaxError: invalid syntax
报错,接下来,进行解决这个问题。
解决方法:按照报错的路径,找到anaconda\lib\site-packages\pyspider
,进入后将run.py
中的async
查找,全部替换为_async
。之后对同目录下其他文件夹中的py文件执行相同的操作。替换时要注意大小写,只替换async单个单词为_async
。
1、\Lib\site-packages\pyspider\run.py
2、\Lib\site-packages\pyspider\fetcher\tornado_fetcher.py
在替换是要注意:只替换名为async 的变量或参数名。不要图省事选择“全部替换”
重新运行,发现报如下错误:
from werkzeug.wsgi import DispatcherMiddleware
ImportError: cannot import name 'DispatcherMiddleware' from 'werkzeug.wsgi'
或者
ImportError: cannot import name 'DispatcherMiddleware'
发现主要是werkzeug版本不对的问题
python -m pip uninstall werkzeug
python -m pip install werkzeug==0.16.0
若出现如下错误:
ValueError: Invalid configuration:
- Deprecated option 'domaincontroller': use 'http_authenticator.domain_controller' instead.
解决:
找到安装包python36->Lib->site-packages->pyspider->webui->webdav.py。如下图: 修改209行代码,如下:
'domaincontroller': NeedAuthController(app),
修改为:
'http_authenticator':{
'HTTPAuthenticator':NeedAuthController(app),
},
因为版本不兼容,所以替换为2.4.1
pip uninstall wsgidav
pip install wsgidav==2.4.1
如果一直卡在result_worker starting…
,在原窗口不关闭的情况下,重新打开另外一个窗口进行输入pyspider
之后进行关闭,就能够成功运行,如果不行,可以重新打开窗口,反复几次操作下。
如果不行,就去杀死pyspider进程
用途
PySpider带有强大的WebUI、脚本编辑器、任务监控器、项目管理器以及结果处理器,它支持多种数据库后端、多种消息队列、Javascript渲染页面的爬取,使用起来非常的方便。
PySpider的基本功能:
- 提供方便易用的 WebUI 系统,可视化地编写和调式爬虫
- 提供爬取进度监控、爬取结果查看、爬虫项目管理等功能。
- 支持多种后端数据库,如 MySQL、MongoDB、Reids、SQLite、Elasticsearch、PostgreSQL。
- 支持多种消息队列、如 RabbitMQ、Beanstalk、Redis、Kombu。
- 提供优先级控制、失败重试、定时抓取等功能。
- 对接了 PhantomJS、可以抓取 JavaScript 渲染的页面。
- 支持单机和分布式部署、支持 Docker 部署。
PySpider的设计基础是:
- 以python脚本驱动的抓取环模型爬虫
- 通过python脚本进行结构化信息的提取,follow链接调度抓取控制,实现最大的灵活性
- 通过web化的脚本编写、调试环境。web展现调度状态
- 抓取环模型成熟稳定,模块间相互独立,通过消息队列连接,从单进程到多机分布式灵活拓展
PySpider与Scrapy的比较:
- PySpider提供了WebUI,爬虫的编写、调试都是再WebUI中进行。而Scrapy原生是不具备这些功能的,它采取的是代码和命令行操作,但是可通过Portia实现可视化配置。
- PySpider调试非常的方便。WebUI操作便捷直观。Scrapy则是使用parse命令进行调试,其方便程度不及PySpider。
- PySpider支持PhantomJS来进行Javascript渲染也买你的额采集。Scrapy可以对接Scrapy-Splash组件,这需要额外配置。
- PySpider内置了PyQuery作为选择器,Scrapy对接了XPath、CSS选择器和正则匹配。
- PySpider的可扩展程度不足,可配置化程度不高。Scrapy可通过对接Middleware、Pipeline、Extension等组件实现非常强大的功能,模块之间的耦合程度低,可扩展程度极高。
如果想要快速方便地实现一个页面的抓取,使用 pyspider 不失为一个好的选择。如快速抓取某个普通新闻网站的新闻内容。但如果应对反爬程度很强、超大规模的抓取、推荐使用 Scrapy、如抓取封 IP、封账号、高频验证的网站的大规模数据采集。
文档
框架介绍
PySpider 的架构主要分为 Scheduler(调度器)、Fetcher(抓取器)、Processer(处理器)三个部分。整个爬取过程受到 Monitor(监控器)的监控,抓取的结果被 Result Worker(结果处理器)处理。
Scheduler 发起任务调度,Fetcher 负责抓取网页内容,Processer 负责解析网页内容,然后将新生成的 Request 发给 Scheduler 进行调度,将生成的提取结果输出保存。
模块 | 功能 |
---|---|
WebUI | web的可视化任务监控web脚本编写,单步调试异常捕获,log捕获,print捕获等 |
Scheduler | 任务优先级周期定时任务流量控制基于时间周期 或 前链标签(例如更新时间)的重抓取调度 |
Fetcher | dataurl支持,用于假抓取模拟传递method, header, cookie, proxy, etag, last_modified, timeout等抓取调度控制通过适配类似 phantomjs 的webkit引擎支持渲染 |
Processer | 内置的pyquery,以jQuery解析页面在脚本中完全控制调度抓取的各项参数,向后链传递信息异常捕获 |
PySpider 的任务执行流程的逻辑很清晰,具体过程如下所示:
- 每个 PySpider项目对应一个 Python 脚本,该脚本定义了一个 Handler 类,它有一个 on_start() 方法。爬取首先调用 on_start() 方法生成最初的抓取任务,然后发送给 Scheduler。
- Scheduler 将抓取任务分发给 Fetcher 进行抓取,Fetcher 执行并得到响应、随后将响应发送给 Processer。
- Processer 处理响应并提取出新的 URL 生成新的抓取任务,然后通过消息队列的方式通知 Scheduler 当前抓取任务执行情况,并将新生成的抓取任务发送给 Scheduler。如果生成了新的提取结果,则将其发送到结果队列等待 Result Worker 处理。
- Scheduler 接收到新的抓取任务,然后查询数据库,判断其如果是新的抓取任务或者是需要重试的任务就继续进行调度,然后将其发送回 Fetcher 进行抓取。
- 不断重复以上工作、直到所有的任务都执行完毕,抓取结束。
- 抓取结束后、程序会回调 on_finished() 方法,这里可以定义后处理过程。
基本使用
目标
爬取去哪儿旅游网站,并且将网页中的作者,标题等内容,存到到MongoDB中。
启动pyspider
activate python36
pyspider all
可以打开浏览器,输入链接 http://localhost:5000
,会看到WebUI页面,能够用来管理项目、编写代码、在线调试、监控任务等。
创建项目
新建一个项目,点击右边的 Create 按钮,在弹出的浮窗里输入项目的名称和爬取的链接,再点击 Create 按钮,就能够成功创建一个项目。
接下来会看到 pyspider 的项目编辑和调试页面用法详解
左侧就是代码的调试页面,点击左侧右上角的 run 单步调试爬虫程序;在左侧下半部分可以预览当前的爬取页面;右侧是代码编辑页面,可以直接编辑代码和保存代码,不需要借助于 IDE。
注意右侧,pyspider 已经生成了一段代码,代码如下所示:
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# Created on 2020-09-21 16:14:45
# Project: qunar
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 按钮部分有 228 的数字标记,这代表新生成了 228 个爬取请求,同时这些请求的 URL 都呈现在当前页面了。
再点击下方的 web 按钮,即可预览当前爬取结果的页面。
当前看到的页面结果和浏览器看到的几乎是完全一致的,在这里可以方便地查看页面请求的结果。
如果pyspider WEB显示框太小,解决如下:
WEB预览框过小的原因在于页面元素的css属性height被替换为60px;
CSS文件所在地方:..\Lib\site-packages\pyspider\webui\static\debug.min.css
打开此文件,搜索iframe,将其修改为:
iframe{border-width:0;width:100%;height:900px !important}
因为注明了!important,所以height=900不会被替换,修改后如下图:
最后,因为 pyspider 有缓存机制,虽然替换了css文件,如果没有清空缓存的话pyspider还是使用原来的css文件。所以记得浏览器清空缓存。清空浏览器缓存后重启浏览器就可以看到正常的WEB大小了。
点击 html 按钮即可查看当前页面的源代码
刚才在 index_page() 方法中提取了所有的链接并生成了新的爬取请求。但是很明显要爬取的肯定不是所有链接,只需要攻略详情的页面链接就够了,所以要修改一下当前 index_page() 里提取链接时的 CSS 选择器。
接下来需要另外一个工具。首先切换到 Web 页面,找到攻略的标题,点击下方的 enable css selector helper,点击标题。这时候看到标题外多了一个红框,上方出现了一个 CSS 选择器,这就是当前标题对应的 CSS 选择器。
在右侧代码选中要更改的区域,点击左栏的右箭头,此时在上方出现的标题的 CSS 选择器就会被替换到右侧代码中
重新点击左栏右上角的 run 按钮,即可重新执行 index_page() 方法。此时的 follows 就变成了 10 个。
现在抓取的只是第一页的内容,还需要抓取后续页面,所以还需要一个爬取链接,即爬取下一页的攻略列表页面。再利用 crawl() 方法添加下一页的爬取请求,在 index_page() 方法里面添加如下代码,然后点击 save 保存:
@config(age=10 * 24 * 60 * 60)
def index_page(self, response):
for each in response.doc('li > .tit > a').items():
self.crawl(each.attr.href, callback=self.detail_page)
next = response.doc('.next').attr.href
self.crawl(next, callback=self.index_page)
利用 CSS 选择器选中下一页的链接,获取它的 href 属性,也就获取了页面的 URL。然后将该 URL 传给 crawl() 方法,同时指定回调函数,注意这里回调函数仍然指定为 index_page() 方法,因为下一页的结构与此页相同。
重新点击 run 按钮,这时就可以看到 11 个爬取请求。follows 按钮上会显示 11,这就代表成功添加了下一页的爬取请求。
爬取详情页
任意选取一个详情页进入,点击10 个爬取请求中的任意一个的右箭头,执行详情页的爬取。
切换到 Web 页面预览效果,页面下拉之后,头图正文中的一些图片一直显示加载中。
出现此现象的原因是 pyspider 默认发送 HTTP 请求,请求的 HTML 文档本身就不包含 img 节点。但是在浏览器中看到了图片,这是因为这张图片是后期经过 JavaScript 出现的。那么,该如何获取?
由于,pyspider 内部对接了 PhantomJS,只需要修改一个参数即可。
将 index_page() 中生成抓取详情页的请求方法添加一个参数 fetch_type,改写的 index_page() 变为如下内容:
@config(age=10 * 24 * 60 * 60)
def index_page(self, response):
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)
点击左栏上方的左箭头返回,重新调用 index_page() 方法生成新的爬取详情页的 Request
再点击新生成的详情页 Request 的爬取按钮,图片就能够加载出来了。
最后就是将详情页中需要的信息提取出来,通过改写最detail_page() 方法,如下:
@config(priority=2)
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 格式。
用法详解
命令行详解
命令行还有很多可配制参数,完整的命令行结构如下所示:
pyspider [OPTIONS] COMMAND [ARGS]
其中,OPTIONS 为可选参数,它可以指定如下参数。
Options:
-c, --config FILENAME 指定配置文件名称
--logging-config TEXT 日志配置文件名称,默认: pyspider/pyspider/logging.conf
--debug 开启调试模式
--queue-maxsize INTEGER 队列的最大长度
--taskdb TEXT taskdb 的数据库连接字符串,默认: sqlite
--projectdb TEXT projectdb 的数据库连接字符串,默认: sqlite
--resultdb TEXT resultdb 的数据库连接字符串,默认: sqlite
--message-queue TEXT 消息队列连接字符串,默认: multiprocessing.Queue
--phantomjs-proxy TEXT PhantomJS 使用的代理,ip:port 的形式
--data-path TEXT 数据库存放的路径
--version pyspider 的版本
--help 显示帮助信息
例如,-c 可以指定配置文件的名称,这是一个常用的配置,配置文件的样例结构如下所示:
{
"taskdb": "mysql+taskdb://username:password@host:port/taskdb",
"projectdb": "mysql+projectdb://username:password@host:port/projectdb",
"resultdb": "mysql+resultdb://username:password@host:port/resultdb",
"message_queue": "amqp://username:password@host:port/%2F",
"webui": {
"username": "some_name",
"password": "some_passwd",
"need-auth": true
}
}
如果要配置 pyspider WebUI 的访问认证,可以新建一个 pyspider.json,内容如下所示:
{
"webui": {
"username": "root",
"password": "123456",
"need-auth": true
}
}
通过在启动时指定配置文件来配置 pyspider WebUI 的访问认证,用户名为 root,密码为 123456,命令如下所示:
pyspider -c pyspider.json all
运行 Scheduler 的命令如下所示:
pyspider scheduler [OPTIONS]
运行时也可以指定各种配置,参数如下所示:
Options:
--xmlrpc /--no-xmlrpc
--xmlrpc-host TEXT
--xmlrpc-port INTEGER
--inqueue-limit INTEGER 任务队列的最大长度,如果满了则新的任务会被忽略
--delete-time INTEGER 设置为 delete 标记之前的删除时间
--active-tasks INTEGER 当前活跃任务数量配置
--loop-limit INTEGER 单轮最多调度的任务数量
--scheduler-cls TEXT Scheduler 使用的类
--help 显示帮助信息
运行 Fetcher 的命令如下所示:
pyspider fetcher [OPTIONS]
参数配置如下所示:
Options:
--xmlrpc /--no-xmlrpc
--xmlrpc-host TEXT
--xmlrpc-port INTEGER
--poolsize INTEGER 同时请求的个数
--proxy TEXT 使用的代理
--user-agent TEXT 使用的 User-Agent
--timeout TEXT 超时时间
--fetcher-cls TEXT Fetcher 使用的类
--help 显示帮助信息
运行 Processer 的命令如下所示:
pyspider processor [OPTIONS]
参数配置如下所示:
Options:
--processor-cls TEXT Processor 使用的类
--help 显示帮助信息
运行 WebUI 的命令如下所示:
pyspider webui [OPTIONS]
参数配置如下所示:
Options:
--host TEXT 运行地址
--port INTEGER 运行端口
--cdn TEXT JS 和 CSS 的 CDN 服务器
--scheduler-rpc TEXT Scheduler 的 xmlrpc 路径
--fetcher-rpc TEXT Fetcher 的 xmlrpc 路径
--max-rate FLOAT 每个项目最大的 rate 值
--max-burst FLOAT 每个项目最大的 burst 值
--username TEXT Auth 验证的用户名
--password TEXT Auth 验证的密码
--need-auth 是否需要验证
--webui-instance TEXT 运行时使用的 Flask 应用
--help 显示帮助信息
这里的配置和前面提到的配置文件参数是相同的。如果想要改变 WebUI 的端口为 5001,单独运行如下命令:
pyspider webui --port 5001
或者可以将端口配置到 JSON 文件中,配置如下所示:
{
"webui": {"port": 5001}
}
使用如下命令启动同样可以达到相同的效果:
pyspider -c pyspider.json webui
这样就可以在 5001 端口上运行 WebUI 了。
crawl()方法
格式:
self.crawl(url=, callback=,age=,
priority=,exetime=,
retries=,itag=,
auto_recrawl=,method=,
...
)
参数 | 说明 |
---|---|
url | 爬取时的 URL,可以定义为单个 URL 字符串,也可以定义成 URL 列表。 |
callback | 回调函数,指定了该 URL 对应的响应内容通过使用哪个方法来解析。 |
age | 任务的有效时间。如果某个任务在有效时间内且已经被执行,则它不会重复执行。可以通过config进行设置,例如:@config(age=10 * 24 * 60 * 60)。 |
priority | 爬取任务的优先级,其值默认是 0,priority 的数值越大,对应的请求会越优先被调度。 |
exetime | 可以设置定时任务,其值是时间戳,默认是 0,即代表立即执行。例如:exetime=time.time()+30*60。 |
retries | 定义重试次数,其值默认是 3。 |
itag | 设置判定网页是否发生变化的节点值,在爬取时会判定次当前节点是否和上次爬取到的节点相同。如果节点相同,则证明页面没有更新,就不会重复爬取。例如:itag=item.find('.update-time').text() 。 |
auto_recrawl | 当值为True时,爬取任务在过期后会重新执行,循环时间即定义的 age 时间长度。 |
method | HTTP 请求方式,它默认是 GET。如果想发起 POST 请求,可以将 method 设置为 POST。 |
params | GET 请求参数,配合method使用。 |
data | POST 表单数据。当请求方式为 POST 时,可以通过此参数传递表单数据,配合method使用。 |
files | 上传的文件,需要指定文件名,配合method使用。 |
user_agent | 爬取使用的 User-Agent |
headers | Request Headers |
cookies | 爬取时使用的 Cookies,为字典格式 |
connect_timeout | 在初始化连接时的最长等待时间,它默认是 20 秒 |
timeout | 抓取网页时的最长等待时间,默认是 120 秒。 |
allow_redirects | 确定是否自动处理重定向,默认是 True |
validate_cert | 确定是否验证证书,此选项对 HTTPS 请求有效,默认是 True。 |
proxy | 爬取时使用的代理,它支持用户名密码的配置,格式为 username:password@hostname:por t。可以设置 craw_config 来实现全局配置, |
fetch_type | 开启 PhantomJS 渲染。如果遇到 JavaScript 渲染的页面,指定此字段即可实现 PhantomJS 的对接,pyspider 将会使用 PhantomJS 进行网页的抓取。 |
js_script | 页面加载完毕后执行的 JavaScript 脚本。例如:js_script='''function() {window.scrollTo(0,document.body.scrollHeight); return 123; } ''' |
js_run_at | 代表 JavaScript 脚本运行的位置,是在页面节点开头还是结尾,默认是结尾,即 document-end。 |
js_viewport_width/js_viewport_height | JavaScript 渲染页面时的窗口大小。 |
load_images | 加载 JavaScript 页面时确定是否加载图片,它默认是否 |
save | 可以在不同的方法之间传递参数。 |
cancel | 取消任务,如果一个任务是 ACTIVE 状态的,则需要将 force_update 设置为 True。 |
force_update | 即使任务处于 ACTIVE 状态,那也会强制更新状态。 |
任务区分
在 pyspider 判断两个任务是否是重复的是使用的是该任务对应的 URL 的 MD5 值作为任务的唯一 ID,如果 ID 相同,那么两个任务就会判定为相同,其中一个就不会爬取了。很多情况下请求的链接可能是同一个,但是 POST 的参数不同。这时可以重写 task_id() 方法,改变这个 ID 的计算方式来实现不同任务的区分,如下所示:
import json
from pyspider.libs.utils import md5string
def get_taskid(self, task):
return md5string(task['url']+json.dumps(task['fetch'].get('data', '')))
这里重写了 get_taskid() 方法,利用 URL 和 POST 的参数来生成 ID。这样一来,即使 URL 相同,但是 POST 的参数不同,两个任务的 ID 就不同,它们就不会被识别成重复任务。
全局配置
pyspider 可以使用 crawl_config 来指定全局的配置,配置中的参数会和 crawl() 方法创建任务时的参数合并。如要全局配置一个 Headers,可以定义如下代码:
class Handler(BaseHandler):
crawl_config = {
'headers': {'User-Agent': 'GoogleBot',}
}
定时爬取
可以通过 every 属性来设置爬取的时间间隔,如下所示:
# 这里设置了每天执行一次爬取
@every(minutes=24 * 60)
def on_start(self):
for url in urllist:
self.crawl(url, callback=self.index_page)
由于在有效时间内爬取不会重复。所以要把有效时间设置得比重复时间更短,这样才可以实现定时爬取。
例如,下面的代码就无法做到每天爬取:
@every(minutes=24 * 60)
def on_start(self):
self.crawl('http://www.example.org/', callback=self.index_page)
@config(age=10 * 24 * 60 * 60)
def index_page(self):
pass
这里任务的过期时间为 10 天,而自动爬取的时间间隔为 1 天。当第二次尝试重新爬取的时候,pyspider 会监测到此任务尚未过期,便不会执行爬取,所以需要将 age 设置得小于定时时间。
项目状态
每个项目都有 6 个状态,分别是 TODO、STOP、CHECKING、DEBUG、RUNNING、PAUSE。
- TODO:它是项目刚刚被创建还未实现时的状态。
- STOP:如果想停止某项目的抓取,可以将项目的状态设置为 STOP。
- CHECKING:正在运行的项目被修改后就会变成 CHECKING 状态,项目在中途出错需要调整的时候会遇到这种情况。
- DEBUG/RUNNING:这两个状态对项目的运行没有影响,状态设置为任意一个,项目都可以运行,但是可以用二者来区分项目是否已经测试通过。
- PAUSE:当爬取过程中出现连续多次错误时,项目会自动设置为 PAUSE 状态,并等待一定时间后继续爬取。
删除项目
pyspider 中没有直接删除项目的选项。如要删除任务,那么将项目的状态设置为 STOP,将分组的名称设置为 delete,等待 24 小时,则项目会自动删除。