7.爬虫框架

爬虫框架

Scrapy

安装
anaconda安装

如果已经安装好了 Anaconda,那么可以通过 conda 命令安装 Scrapy,安装命令如下:

conda install Scrapy

运行之后便可以完成 Scrapy 的安装。

其他安装
pip install scrapy
验证

image-20200918120450864

用途

适用于抓取大量数据的时候。

文档

中文文档

官方文档

框架介绍
架构

img

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

这里各个文件的功能描述如下:

  • 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

文件夹结构如下所示:

image-20200918143016256

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/

image-20200918151014316

进入刚才创建的 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

image-20200918152621172

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)。

爬取步骤:

  1. 以初始的URL初始化Request,并设置回调函数。 当该request下载完毕并返回时,将生response,并作为参数传给该回调函数。
  2. spider中初始的request是通过调用 start_requests() 来获取的。 start_requests() 读取 start_urls 中的URL, 并以 parse 为回调函数生成 Request
  3. 在回调函数内分析返回的(网页)内容,返回 Item 对象、dictRequest 或者一个包括三者的可迭代容器。 返回的Request对象之后会经过Scrapy处理,下载相应的内容,并调用设置的callback函数(函数可相同)。 在回调函数内,可以使用 选择器(Selectors) (也可以使用BeautifulSoup, pyquery或者想用的任何解析器) 来分析网页内容,并根据分析的数据生成item。
  4. 最后,由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 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 的主页

image-20200918225544384

当然 Splash 也可以直接安装在远程服务器上,在服务器上运行以守护态运行 Splash 即可,命令如下:

docker run -d -p 8050:8050 scrapinghub/splash

在这里多了一个 -d 参数,它代表将 Docker 容器以守护态运行,这样在中断远程服务器连接后不会终止 Splash 服务的运行。

用途

一个页面渲染服务器,返回渲染后的页面,便于爬取,便于规模应用。

文档

splash官方文档

lua中文文档

lua下载

用法
实例

img

脚本语言内容:

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

img

  • 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秒之内没有得到响应就会抛出异常

img

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

img

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

img

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

img

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进程

image-20200921010813402

image-20200921010904589

用途

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(结果处理器)处理。

img

Scheduler 发起任务调度,Fetcher 负责抓取网页内容,Processer 负责解析网页内容,然后将新生成的 Request 发给 Scheduler 进行调度,将生成的提取结果输出保存。

模块功能
WebUIweb的可视化任务监控web脚本编写,单步调试异常捕获,log捕获,print捕获等
Scheduler任务优先级周期定时任务流量控制基于时间周期 或 前链标签(例如更新时间)的重抓取调度
Fetcherdataurl支持,用于假抓取模拟传递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页面,能够用来管理项目、编写代码、在线调试、监控任务等。

image-20200921161151129

创建项目

新建一个项目,点击右边的 Create 按钮,在弹出的浮窗里输入项目的名称和爬取的链接,再点击 Create 按钮,就能够成功创建一个项目。

image-20200921161442722

接下来会看到 pyspider 的项目编辑和调试页面用法详解

image-20200921161527311

左侧就是代码的调试页面,点击左侧右上角的 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(),
        }

详解用法

说明
Handlerpyspider 爬虫的主类,可以在此处定义爬取、解析、存储的逻辑。整个爬虫的功能只需要一个 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,这代表有新的爬取请求产生。

image-20200921162847728

左栏左上角会出现当前 run 的配置文件,这里有一个 callback 为 on_start,这说明点击 run 之后实际是执行了 on_start() 方法。在 on_start() 方法中,利用 crawl() 方法生成一个爬取请求,那下方 follows 部分的数字 1 就代表了这一个爬取请求。

点击下方的 follows 按钮,即可看到生成的爬取请求的链接。每个链接的右侧还有一个箭头按钮。

image-20200921162959133

点击该箭头,就可以对此链接进行爬取,也就是爬取的首页内容。

image-20200921163022776

上方的 callback 已经变成了 index_page,这就代表当前运行了 index_page() 方法。index_page() 接收到的 response 参数就是刚才生成的第一个爬取请求的 Response 对象。index_page() 方法通过调用 doc() 方法,传入提取所有 a 节点的 CSS 选择器,然后获取 a 节点的属性 href,这样实际上就是获取了第一个爬取页面中的所有链接。然后在 index_page() 方法里遍历了所有链接,同时调用 crawl() 方法,就把这一个个的链接构造成新的爬取请求了。所以最下方 follows 按钮部分有 228 的数字标记,这代表新生成了 228 个爬取请求,同时这些请求的 URL 都呈现在当前页面了。

再点击下方的 web 按钮,即可预览当前爬取结果的页面。

image-20200921163733856

当前看到的页面结果和浏览器看到的几乎是完全一致的,在这里可以方便地查看页面请求的结果。

如果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不会被替换,修改后如下图:

image-20200921164520443

最后,因为 pyspider 有缓存机制,虽然替换了css文件,如果没有清空缓存的话pyspider还是使用原来的css文件。所以记得浏览器清空缓存。清空浏览器缓存后重启浏览器就可以看到正常的WEB大小了。

image-20200921164620907

点击 html 按钮即可查看当前页面的源代码

image-20200921163810801

刚才在 index_page() 方法中提取了所有的链接并生成了新的爬取请求。但是很明显要爬取的肯定不是所有链接,只需要攻略详情的页面链接就够了,所以要修改一下当前 index_page() 里提取链接时的 CSS 选择器。

接下来需要另外一个工具。首先切换到 Web 页面,找到攻略的标题,点击下方的 enable css selector helper,点击标题。这时候看到标题外多了一个红框,上方出现了一个 CSS 选择器,这就是当前标题对应的 CSS 选择器。

image-20200921164720959

在右侧代码选中要更改的区域,点击左栏的右箭头,此时在上方出现的标题的 CSS 选择器就会被替换到右侧代码中

image-20200921164922745

重新点击左栏右上角的 run 按钮,即可重新执行 index_page() 方法。此时的 follows 就变成了 10 个。

image-20200921164955347

现在抓取的只是第一页的内容,还需要抓取后续页面,所以还需要一个爬取链接,即爬取下一页的攻略列表页面。再利用 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,这就代表成功添加了下一页的爬取请求。

image-20200921165339294

爬取详情页

任意选取一个详情页进入,点击10 个爬取请求中的任意一个的右箭头,执行详情页的爬取。

image-20200921165620071

切换到 Web 页面预览效果,页面下拉之后,头图正文中的一些图片一直显示加载中。

image-20200921165656853

出现此现象的原因是 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

image-20200921170209840

再点击新生成的详情页 Request 的爬取按钮,图片就能够加载出来了。

image-20200921170244355

最后就是将详情页中需要的信息提取出来,通过改写最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
    }

重新运行

image-20200921170422504

启动爬虫

返回爬虫的主页面,将爬虫的 status 设置成 DEBUG 或 RUNNING,点击右侧的 Run 按钮即可开始爬取。

image-20200921170542152

在最左侧可以定义项目的分组,以方便管理。

  • rate/burst 代表当前的爬取速率
    • rate 代表 1 秒发出多少个请求
    • burst 相当于流量控制中的令牌桶算法的令牌数

rate 和 burst 设置的越大,爬取速率越快,当然速率需要考虑本机性能和爬取过快被封的问题。

  • process 中的 5m、1h、1d 指的是最近 5 分、1 小时、1 天内的请求情况
  • all 代表所有的请求情况,请求由不同颜色表示。
    • 蓝色的代表等待被执行的请求
    • 绿色的代表成功的请求
    • 黄色的代表请求失败后等待重试的请求
    • 红色的代表失败次数过多而被忽略的请求

这样可以直观知道爬取的进度和请求情况。

image-20200921170752404

点击 Active Tasks,即可查看最近请求的详细状况

image-20200921170813182

点击 Results,即可查看所有的爬取结果

image-20200921170827515

点击右上角的按钮,即可获取数据的 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 时间长度。
methodHTTP 请求方式,它默认是 GET。如果想发起 POST 请求,可以将 method 设置为 POST。
paramsGET 请求参数,配合method使用。
dataPOST 表单数据。当请求方式为 POST 时,可以通过此参数传递表单数据,配合method使用。
files上传的文件,需要指定文件名,配合method使用。
user_agent爬取使用的 User-Agent
headersRequest Headers
cookies爬取时使用的 Cookies,为字典格式
connect_timeout在初始化连接时的最长等待时间,它默认是 20 秒
timeout抓取网页时的最长等待时间,默认是 120 秒。
allow_redirects确定是否自动处理重定向,默认是 True
validate_cert确定是否验证证书,此选项对 HTTPS 请求有效,默认是 True。
proxy爬取时使用的代理,它支持用户名密码的配置,格式为 username:password@hostname:port。可以设置 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_heightJavaScript 渲染页面时的窗口大小。
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 小时,则项目会自动删除。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值