------------------------------------------------------------------------------------------
scrapy 中文文档 和 scrapy 英文文档参照看。因为中文文档比较老,英文文档是最新的。
scrapy 英文文档:https://doc.scrapy.org/en/latest
scrapy 中文文档:http://scrapy-chs.readthedocs.io/zh_CN/1.0/intro/overview.html
高性能爬虫 Scrapy 框架:https://www.cnblogs.com/wwg945/articles/9021888.html
------------------------------------------------------------------------------------------
scrapy-redis 配置:https://www.cnblogs.com/wwg945/articles/9046232.html
基于 Scrapy-redis 的分布式爬虫设计:https://www.jianshu.com/p/cd4054bbc757
------------------------------------------------------------------------------------------
Scrapy爬虫入门教程:https://www.jianshu.com/p/43029ea38251
------------------------------------------------------------------------------------------
scarpy 是一个爬虫框架,而 scrapyd 是一个网页版管理 scrapy 的工具, scrapy爬虫写好后,可以用命令行运行,但是如果能在网页上操作就比较方便。 scrapyd 就是为了解决这个问题,能够在网页端查看正在执行的任务,也能新建爬虫任务,和终止爬虫任务,功能比较强大。 还有一个更加强大的国产工具 gerapy
Scrapyd 使用详解:https://blog.csdn.net/fengltxx/article/details/79889340
Gerapy分布式爬虫管理框架:https://cuiqingcai.com/4959.html
scrapy_redis去重优化(已有7亿条数据),附Demo福利:https://blog.csdn.net/bone_ace/article/details/53099042
scrapy 框架系列教程:http://www.cnblogs.com/xinyangsdut/p/7628770.html
scrapy 系列教程 - 设置随机 user-agent:https://www.cnblogs.com/cnkai/p/7401343.html
Python分布式爬虫打造搜索引擎Scrapy精讲 系列教程—将bloomfilter(布隆过滤器)集成到scrapy-redis中
https://www.cnblogs.com/adc8868/p/7442306.html
scrapy-redis分布式爬虫的搭建过程:https://blog.csdn.net/zwq912318834/article/details/78854571
Scrapy第三篇(基于Scrapy-Redis的分布式以及cookies池):https://cuiqingcai.com/4048.html
scrapy 下载中间件用法:https://www.cnblogs.com/zhaof/p/7198407.html
scrapy 设置代理:使用 IP 代理池 middleware设置proxy代理
关于 scrapy 重定向: 禁止重定向的解决方法:https://www.zhaokeli.com/article/8046.html
https://blog.csdn.net/amaomao123/article/details/52511882
内容都是从官方文档整理过来的,只整理一部分,要想深入了解,可以看官方文档
scrapy整体架构图
Scrapy主要组件
1、引擎(Scrapy):用来处理整个系统的数据流处理, 触发事务(框架核心)。
2、调度器(Scheduler):用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址。
3、下载器(Downloader): 用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)。
4、爬虫(Spiders): 爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面。
5、项目管道(Pipeline): 负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。
6、下载器中间件(Downloader Middlewares): 位于Scrapy引擎和下载器之间的框架,主要是处理Scrapy引擎与下载器之间的请求及响应。
7、爬虫中间件(Spider Middlewares): 介于Scrapy引擎和爬虫之间的框架,主要工作是处理蜘蛛的响应输入和请求输出。
8、调度中间件(Scheduler Middewares): 介于Scrapy引擎和调度之间的中间件,从Scrapy引擎发送到调度的请求和响应。
表格:
组件 | 功能 |
---|---|
引擎(Scrapy) | 用来处理整个系统的数据流处理, 触发事务(框架核心) |
调度器(Scheduler) | 用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址 |
下载器(Downloader) | 用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted上的) |
爬虫(Spiders) | 爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面 |
项目管道(Pipeline) | 负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据 |
下载器中间件(Downloader Middlewares) | 位于Scrapy引擎和下载器之间的框架,主要是处理Scrapy引擎与下载器之间的请求及响应 |
爬虫中间件(Spider Middlewares) | 介于Scrapy引擎和爬虫之间的框架,主要工作是处理蜘蛛的响应输入和请求输出 |
调度中间件(Scheduler Middewares) | 介于Scrapy引擎和调度之间的中间件,从Scrapy引擎发送到调度的请求和响应 |
Scrapy 框架 的 运行流程:
- 首先,引擎 从 调度器 中 取出一个链接(URL) 用于 接下来的抓取
- 引擎 把 URL封装成一个 请求(Request) 传给下载器
- 下载器 把 资源下载下来,并 封装成 应答包(Response)
- 爬虫(指自己写的爬虫程序Spider) 解析 应答包(Response)
- 如果 爬虫 从 Response 中 解析出的是实体(Item),则把 Item(实体) 交给 管道 进行进一步的处理。
- 如果 爬虫 从 Response 中 解析出的是链接(URL),则把 URL 交给 Scheduler 等待抓取
scrapy 框架 中 的 spider 爬取流程
对 spider 来说,爬取的循环类似下文:
-
以初始的URL初始化Request,并设置回调函数。 当该request下载完毕并返回时,将生成response,并作为参数传给该回调函数。
spider中初始的request是通过调用
start_requests()
来获取的。start_requests()
读取start_urls
中的URL, 并以parse
为回调函数生成Request
。 -
在回调函数内分析返回的(网页)内容,返回
Item
对象、dict、Request
或者一个包括三者的可迭代容器。 返回的Request对象之后会经过Scrapy处理,下载相应的内容,并调用设置的callback函数(函数可相同)。 -
在回调函数内,您可以使用 选择器(Selectors) (您也可以使用BeautifulSoup, lxml 或者您想用的任何解析器) 来分析网页内容,并根据分析的数据生成item。
-
最后,由spider返回的item将被存到数据库(由某些 Item Pipeline 处理)或使用 Feed exports 存入到文件中。
虽然该循环对任何类型的spider都(多少)适用,但Scrapy仍然为了不同的需求提供了多种默认spider。
可以查看官方文档了解更多关于不同需求的spider。
初窥Scrapy
Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。其最初是为了 网络抓取 所设计的, 也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services ) 或者通用的网络爬虫。
一窥示例spider
为了让您了解Scrapy提供了什么功能,我们将提供一个Scrapy Spider的示例,并且以最简单的方式启动该spider。以下的代码将跟进StackOverflow上具有投票数最多的链接,并且爬取其中的一些数据:
import scrapyclass StackOverflowSpider(scrapy.Spider):name = 'stackoverflow'start_urls = ['http://stackoverflow.com/questions?sort=votes']def parse(self, response):for href in response.css('.question-summary h3 a::attr(href)'):full_url = response.urljoin(href.extract())yield scrapy.Request(full_url, callback=self.parse_question)def parse_question(self, response):yield {'title': response.css('h1 a::text').extract()[0],'votes': response.css('.question .vote-count-post::text').extract()[0],'body': response.css('.question .post-text').extract()[0],'tags': response.css('.question .post-tag::text').extract(),'link': response.url,}
将上述代码存入到某个文件中,以类似于 stackoverflow_spider.py
命名, 并且使用 runspider
命令运行:
scrapy runspider stackoverflow_spider.py -o top-stackoverflow-questions.json
当命令执行完后,您将会得到 top-stackoverflow-questions.json
文件。 该文件以JSON格式保存了StackOverflow上获得upvote最多的问题, 包含了标题、链接、upvote的数目、相关的tags以及以HTML格式保存的问题内容, 看起来类似于这样(为了更容易阅读,对内容进行重新排版):
[{"body": "... LONG HTML HERE ...","link": "http://stackoverflow.com/questions/11227809/why-is-processing-a-sorted-array-faster-than-an-unsorted-array","tags": ["java", "c++", "performance", "optimization"],"title": "Why is processing a sorted array faster than an unsorted array?","votes": "9924"
},
{"body": "... LONG HTML HERE ...","link": "http://stackoverflow.com/questions/1260748/how-do-i-remove-a-git-submodule","tags": ["git", "git-submodules"],"title": "How do I remove a Git submodule?","votes": "1764"
},
...]
Here’s the code for a spider that scrapes famous quotes from website http://quotes.toscrape.com, following the pagination:
import scrapyclass QuotesSpider(scrapy.Spider):name = "quotes"start_urls = ['http://quotes.toscrape.com/tag/humor/',]def parse(self, response):for quote in response.css('div.quote'):yield {'text': quote.css('span.text::text').extract_first(),'author': quote.xpath('span/small/text()').extract_first(),}next_page = response.css('li.next a::attr("href")').extract_first()if next_page is not None:next_page = response.urljoin(next_page)yield scrapy.Request(next_page, callback=self.parse)
将上述代码存入到某个文件中,以类似于 quotes_spider.py
命名, 并且使用 runspider
命令运行:
scrapy runspider quotes_spider.py -o quotes.json
刚刚发生了什么?
当您运行 scrapy runspider somefile.py
命令时,Scrapy尝试从该文件中查找Spider的定义,并且在爬取引擎中运行它。
Scrapy首先读取定义在 start_urls
属性中的URL(在本示例中,就是StackOverflow的top question页面的URL), 创建请求,并且将接收到的response作为参数调用默认的回调函数 parse
,来启动爬取。 在回调函数 parse
中,我们使用CSS Selector来提取链接。接着,我们产生(yield)更多的请求, 注册 parse_question
作为这些请求完成时的回调函数。
这里,您可以注意到Scrapy的一个最主要的优势: 请求(request)是 被异步调度和处理的 。 这意味着,Scrapy并不需要等待一个请求(request)完成及处理,在此同时, 也发送其他请求或者做些其他事情。 这也意味着,当有些请求失败或者处理过程中出现错误时,其他的请求也能继续处理。
在允许您可以以非常快的速度进行爬取时(以容忍错误的方式同时发送多个request), Scrapy也通过 一些设置 来允许您控制其爬取的方式。 例如,您可以为两个request之间设置下载延迟, 限制单域名(domain)或单个IP的并发请求量,甚至可以 使用自动限制插件 来自动处理这些问题。
最终, parse_question
回调函数从每个页面中爬取到问题(question)的数据并产生了一个dict, Scrapy收集并按照终端(command line)的要求将这些结果写入到了JSON文件中。
注解
这里使用了 feed exports 来创建了JSON文件, 您可以很容易的改变导出的格式(比如XML或CSV)或者存储后端(例如FTP或者 Amazon S3)。 您也可以编写 item pipeline 来将item存储到数据库中。
还有什么?
您已经了解了如何通过Scrapy提取存储网页中的信息,但这仅仅只是冰山一角。Scrapy提供了很多强大的特性来使得爬取更为简单高效, 例如:
- 对HTML, XML源数据 选择及提取 的内置支持, 提供了CSS选择器(selector)以及XPath表达式进行处理, 以及一些帮助函数(helper method)来使用正则表达式来提取数据.
- 提供 交互式shell终端 , 为您测试CSS及XPath表达式,编写和调试爬虫提供了极大的方便
- 通过 feed导出 提供了多格式(JSON、CSV、XML),多存储后端(FTP、S3、本地文件系统)的内置支持
- 提供了一系列在spider之间共享的可复用的过滤器(即 Item Loaders),对智能处理爬取数据提供了内置支持。
- 针对非英语语系中不标准或者错误的编码声明, 提供了自动检测以及健壮的编码支持。
- 高扩展性。您可以通过使用 signals ,设计好的API(中间件, extensions, pipelines)来定制实现您的功能。
- 内置的中间件及扩展为下列功能提供了支持: * cookies and session 处理 * HTTP 压缩 * HTTP 认证 * HTTP 缓存 * user-agent模拟 * robots.txt * 爬取深度限制 * 其他
- 内置 Telnet终端 ,通过在Scrapy进程中钩入Python终端,使您可以查看并且调试爬虫
- 以及其他一些特性,例如可重用的,从 Sitemaps 及 XML/CSV feeds中爬取网站的爬虫、 可以 自动下载 爬取到的数据中的图片(或者其他资源)的media pipeline、 带缓存的DNS解析器,以及更多的特性。
安装指南
安装Scrapy
官网英文版安装向导 官网中文版安装向导
基本使用
基本命令
1. scrapy startproject 项目名称- 在当前目录中创建中创建一个项目文件(类似于Django)2. scrapy genspider [-t template] <name> <domain>- 创建爬虫应用如:scrapy gensipider oldboy oldboy.comscrapy gensipider -t xmlfeed autohome autohome.com.cnPS:查看所有命令:scrapy gensipider -l查看模板命令:scrapy gensipider -d 模板名称3. scrapy list- 展示爬虫应用列表4. scrapy crawl 爬虫应用名称- 运行单独爬虫应用
项目结构以及爬虫应用简介
project_name/scrapy.cfgproject_name/__init__.pyitems.pypipelines.pysettings.pyspiders/__init__.py爬虫1.py爬虫2.py爬虫3.py
文件说明:
- scrapy.cfg 项目的主配置信息。(真正爬虫相关的配置信息在settings.py文件中)
- items.py 设置数据存储模板,用于结构化数据,如:Django的Model
- pipelines 数据处理行为,如:一般结构化的数据持久化
- settings.py 配置文件,如:递归的层数、并发数,延迟下载等
- spiders 爬虫目录,如:创建文件,编写爬虫规则
爬虫1.py
import scrapyclass XiaoHuarSpider(scrapy.spiders.Spider):name = "xiaohuar" # 爬虫名称 *****allowed_domains = ["xiaohuar.com"] # 允许的域名start_urls = ["http://www.xiaohuar.com/hua/", # 其实URL]def parse(self, response):# 访问起始URL并获取结果后的回调函数# windows编码问题在头部添加下面两句
import sys,os
sys.stdout=io.TextIOWrapper(sys.stdout.buffer,encoding='gb18030')
Scrapy入门教程
在本篇教程中,我们假定您已经安装好Scrapy。
本篇教程中将带您完成下列任务:
- 创建一个Scrapy项目
- 定义提取的Item
- 编写爬取网站的 spider 并提取 Item
- 编写 Item Pipeline 来存储提取到的Item(即数据)
Scrapy由 Python 编写。如果您刚接触并且好奇这门语言的特性以及Scrapy的详情, 对于已经熟悉其他语言并且想快速学习Python的编程老手, 我们推荐 Learn Python The Hard Way 、 Dive Into Python 3,或者 跟随 Python Tutorial。 对于想从Python开始学习的编程新手, 非程序员的Python学习资料列表 将是您的选择。
创建项目
在开始爬取之前,您必须创建一个新的Scrapy项目。 进入您打算存储代码的目录中,运行下列命令:
scrapy startproject tutorial
该命令将会创建包含下列内容的 tutorial
目录:
tutorial/scrapy.cfgtutorial/__init__.pyitems.pypipelines.pysettings.pyspiders/__init__.py...
创建项目与配置环境后各部分组件:
定义Item
Item 是保存爬取到的数据的容器;其使用方法和python字典类似。虽然您也可以在Scrapy中直接使用dict,但是 Item 提供了额外保护机制来避免拼写错误导致的未定义字段错误。 They can also be used with Item Loaders, a mechanism with helpers to conveniently populate Items.
类似在ORM中做的一样,您可以通过创建一个 scrapy.Item
类, 并且定义类型为 scrapy.Field
的类属性来定义一个Item。 (如果不了解ORM, 不用担心,您会发现这个步骤非常简单)
首先根据需要从dmoz.org获取到的数据对item进行建模。 我们需要从dmoz中获取名字,url,以及网站的描述。 对此,在item中定义相应的字段。编辑 tutorial
目录中的 items.py
文件:
import scrapyclass DmozItem(scrapy.Item):title = scrapy.Field()link = scrapy.Field()desc = scrapy.Field()
一开始这看起来可能有点复杂,但是通过定义item可以很方便的使用Scrapy的其他方法。而这些方法需要知道您的item的定义。
编写第一个爬虫(Spider)
Spider是用户编写用于从单个网站(或者一些网站)爬取数据的类。其包含了一个用于下载的初始URL,如何跟进网页中的链接以及如何分析页面中的内容, 提取生成 item 的方法。为了创建一个Spider,您必须继承 scrapy.Spider
类, 且定义一些属性:
name
: 用于区别Spider。 该名字必须是唯一的,您不可以为不同的Spider设定相同的名字。start_urls
: 包含了Spider在启动时进行爬取的url列表。 因此,第一个被获取到的页面将是其中之一。 后续的URL则从初始的URL获取到的数据中提取。start_requests()
: must return an iterable of Requests (you can return a list of requests or write a generator function) which the Spider will begin to crawl from. Subsequent requests will be generated successively from these initial requests.parse()
是spider的一个方法。 被调用时,每个初始URL完成下载后生成的Response
对象将会作为唯一的参数传递给该函数。 该方法负责解析返回的数据(response data),提取数据(生成item)以及生成需要进一步处理的URL的Request
对象。
以下为我们的第一个Spider代码,保存在 tutorial/spiders
目录下的 dmoz_spider.py
文件中:
使用 startu_urls 示例:
import scrapyclass DmozSpider(scrapy.Spider):name = "dmoz"allowed_domains = ["dmoz.org"]start_urls = ["http://www.dmoz.org/Computers/Programming/Languages/Python/Books/","http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/"]def parse(self, response):filename = response.url.split("/")[-2] + '.html'with open(filename, 'wb') as f:f.write(response.body)
第二个Spider代码,保存在 tutorial/spiders
目录下的 quotes_spider.py
文件中:
使用 start_requests 函数 示例:
import scrapyclass QuotesSpider(scrapy.Spider):name = "quotes"def start_requests(self):urls = ['http://quotes.toscrape.com/page/1/','http://quotes.toscrape.com/page/2/',]for url in urls:yield scrapy.Request(url=url, callback=self.parse)def parse(self, response):page = response.url.split("/")[-2]filename = 'quotes-%s.html' % pagewith open(filename, 'wb') as f:f.write(response.body)self.log('Saved file %s' % filename)
关闭 Scrapy 的 robot.txt 功能
关闭 scrapy 自带的 ROBOTSTXT_OBEY 功能,在setting找到这个变量,设置为False。
运行程序并爬取数据
进入项目的根目录,执行下列命令启动spider:
scrapy crawl dmoz
或者
scrapy crawl quotes
在 项目 的 根目录 下 多了两个HTML文件
继续在项目的根目录下运行 :scrapy crawl dmoz
刚才发生了什么?
Scrapy 通过 start_requests
方法调度 scrapy.Request
对象,然后根据 request 关联的 回调 函数,把每一个接收到response传给回调函数处理
一个快捷的处理
Instead of implementing a start_requests()
method that generates scrapy.Request
objects from URLs, you can just define a start_urls
class attribute with a list of URLs. This list will then be used by the default implementation of start_requests()
to create the initial requests for your spider:
import scrapyclass QuotesSpider(scrapy.Spider):name = "quotes"start_urls = ['http://quotes.toscrape.com/page/1/','http://quotes.toscrape.com/page/2/',]def parse(self, response):page = response.url.split("/")[-2]filename = 'quotes-%s.html' % pagewith open(filename, 'wb') as f:f.write(response.body)
The parse()
method will be called to handle each of the requests for those URLs, even though we haven’t explicitly told Scrapy to do so. This happens because parse()
is Scrapy’s default callback method, which is called for requests without an explicitly assigned callback.
提取Item
Selectors选择器简介
从网页中提取数据有很多方法。Scrapy使用了一种基于 XPath 和 CSS 表达式机制: Scrapy Selectors。 关于selector和其他提取机制的信息请参考 Selector文档 。
这里给出XPath表达式的例子及对应的含义:
/html/head/title
: 选择HTML文档中<head>
标签内的<title>
元素/html/head/title/text()
: 选择上面提到的<title>
元素的文字//td
: 选择所有的<td>
元素//div[@class="mine"]
: 选择所有具有class="mine"
属性的div
元素
上边仅仅是几个简单的XPath例子,XPath实际上要比这远远强大的多。 如果您想了解的更多,我们推荐 通过这些例子来学习XPath, 以及 这篇教程学习”how to think in XPath”.
CSS vs XPath: 您可以仅仅使用CSS Selector来从网页中 提取数据。不过, XPath提供了更强大的功能。其不仅仅能指明数据所在的路径, 还能查看数据: 比如,您可以这么进行选择: 包含文字 ‘Next Page’ 的链接 。 正因为如此,即使您已经了解如何使用 CSS selector, 我们仍推荐您使用XPath
为了配合CSS与XPath,Scrapy除了提供了 Selector
之外,还提供了方法来避免每次从response中提取数据时生成selector的麻烦。Selector有四个基本的方法(点击相应的方法可以看到详细的API文档):
xpath()
: 传入xpath表达式,返回该表达式所对应的所有节点的selector list列表 。css()
: 传入CSS表达式,返回该表达式所对应的所有节点的selector list列表.extract()
: 序列化该节点为unicode字符串并返回list。re()
: 根据传入的正则表达式对数据进行提取,返回unicode字符串list列表。
在Shell中尝试Selector选择器
为了介绍Selector的使用方法,接下来我们将要使用内置的 Scrapy shell 。Scrapy Shell需要您预装好 IPython (一个扩展的Python终端)。您需要进入项目的根目录,执行下列命令来启动shell:
scrapy shell "http://www.dmoz.org/Computers/Programming/Languages/Python/Books/"
或者
scrapy shell "http://quotes.toscrape.com/page/1/"
当您在终端运行Scrapy时,请一定记得给url地址加上引号,否则包含参数的url(例如 &
字符)会导致Scrapy运行失败。
当shell载入后,您将得到一个包含response数据的本地 response
变量。输入 response.body
将输出response的包体, 输出 response.headers
可以看到response的包头。
#TODO.. 更为重要的是, response
拥有一个 selector
属性, 该属性是以该特定 response
初始化的类 Selector
的对象。 您可以通过使用 response.selector.xpath()
或 response.selector.css()
来对 response
进行查询。 此外,scrapy也对 response.selector.xpath()
及 response.selector.css()
提供了一些快捷方式, 例如 response.xpath()
或 response.css()
,
同时,shell根据response提前初始化了变量 sel
。该selector根据response的类型自动选择最合适的分析规则(XML vs HTML)。
In [1]: response.xpath('//title')
Out[1]: [<Selector xpath='//title' data=u'<title>Open Directory - Computers: Progr'>]In [2]: response.xpath('//title').extract()
Out[2]: [u'<title>Open Directory - Computers: Programming: Languages: Python: Books</title>']In [3]: response.xpath('//title/text()')
Out[3]: [<Selector xpath='//title/text()' data=u'Open Directory - Computers: Programming:'>]In [4]: response.xpath('//title/text()').extract()
Out[4]: [u'Open Directory - Computers: Programming: Languages: Python: Books']In [5]: response.xpath('//title/text()').re('(\w+):')
Out[5]: [u'Computers', u'Programming', u'Languages', u'Python']
response.css('title')
response.css('title::text').extract()
response.css('title').extract()
response.css('title::text').extract_first()
response.css('title::text')[0].extract()
response.css('title::text').re(r'Quotes.*')
response.css('title::text').re(r'Q\w+')
response.css('title::text').re(r'(\w+) to (\w+)')
之前提到过,每个 .xpath()
调用返回selector组成的list,因此我们可以拼接更多的 .xpath()
来进一步获取某个节点。我们将在下边使用这样的特性:
for sel in response.xpath('//ul/li'):title = sel.xpath('a/text()').extract()link = sel.xpath('a/@href').extract()desc = sel.xpath('text()').extract()print title, link, desc
关于嵌套selctor的更多详细信息,请参考 嵌套选择器(selectors) 以及 选择器(Selectors) 文档中的 使用相对XPaths 部分。
在我们的spider中加入这段代码:
import scrapyclass DmozSpider(scrapy.Spider):name = "dmoz"allowed_domains = ["dmoz.org"]start_urls = ["http://www.dmoz.org/Computers/Programming/Languages/Python/Books/","http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/"]def parse(self, response):for sel in response.xpath('//ul/li'):title = sel.xpath('a/text()').extract()link = sel.xpath('a/@href').extract()desc = sel.xpath('text()').extract()print title, link, desc
使用item
Item
对象是自定义的python字典。 您可以使用标准的字典语法来获取到其每个字段的值。(字段即是我们之前用Field赋值的属性):
>>> item = DmozItem()
>>> item['title'] = 'Example title'
>>> item['title']
'Example title'
为了将爬取的数据返回,我们最终的代码将是:
import scrapyfrom tutorial.items import DmozItemclass DmozSpider(scrapy.Spider):name = "dmoz"allowed_domains = ["dmoz.org"]start_urls = ["http://www.dmoz.org/Computers/Programming/Languages/Python/Books/","http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/"]def parse(self, response):for sel in response.xpath('//ul/li'):item = DmozItem()item['title'] = sel.xpath('a/text()').extract()item['link'] = sel.xpath('a/@href').extract()item['desc'] = sel.xpath('text()').extract()yield item
您可以在 dirbot 项目中找到一个具有完整功能的spider。该项目可以通过 https://github.com/scrapy/dirbot 找到。
现在对dmoz.org进行爬取将会产生 DmozItem
对象:
提取数据
import scrapyclass QuotesSpider(scrapy.Spider):name = "quotes"start_urls = ['http://quotes.toscrape.com/page/1/','http://quotes.toscrape.com/page/2/',]def parse(self, response):for quote in response.css('div.quote'):yield {'text': quote.css('span.text::text').extract_first(),'author': quote.css('small.author::text').extract_first(),'tags': quote.css('div.tags a.tag::text').extract(),}
If you run this spider, it will output the extracted data with the log:
2016-09-19 18:57:19 [scrapy.core.scraper] DEBUG: Scraped from <200 http://quotes.toscrape.com/page/1/>
{'tags': ['life', 'love'], 'author': 'André Gide', 'text': '“It is better to be hated for what you are than to be loved for what you are not.”'}
2016-09-19 18:57:19 [scrapy.core.scraper] DEBUG: Scraped from <200 http://quotes.toscrape.com/page/1/>
{'tags': ['edison', 'failure', 'inspirational', 'paraphrased'], 'author': 'Thomas A. Edison', 'text': "“I have not failed. I've just found 10,000 ways that won't work.”"}
存储数据
最简单的是通过命令行:scrapy crawl quotes -o quotes.json
该命令将采用 JSON 格式对爬取的数据进行序列化,生成 quotes.json
文件。
在类似本篇教程里这样小规模的项目中,这种存储方式已经足够。 如果需要对爬取到的item做更多更为复杂的操作,您可以编写 Item Pipeline 。 类似于我们在创建项目时对Item做的,用于您编写自己的 tutorial/pipelines.py
也被创建。 不过如果您仅仅想要保存item,您不需要实现任何的pipeline。
追踪链接(Following links)
既然已经能从页面上爬取数据了,为什么不提取您感兴趣的页面的链接,追踪他们, 读取这些链接的数据呢?
下面是实现这个功能的改进版spider:
import scrapyfrom tutorial.items import DmozItemclass DmozSpider(scrapy.Spider):name = "dmoz"allowed_domains = ["dmoz.org"]start_urls = ["http://www.dmoz.org/Computers/Programming/Languages/Python/",]def parse(self, response):for href in response.css("ul.directory.dir-col > li > a::attr('href')"):url = response.urljoin(response.url, href.extract())yield scrapy.Request(url, callback=self.parse_dir_contents)def parse_dir_contents(self, response):for sel in response.xpath('//ul/li'):item = DmozItem()item['title'] = sel.xpath('a/text()').extract()item['link'] = sel.xpath('a/@href').extract()item['desc'] = sel.xpath('text()').extract()yield item
现在, parse() 仅仅从页面中提取我们感兴趣的链接,使用 response.urljoin 方法构造一个绝对路径的URL(页面上的链接都是相对路径的), 产生(yield)一个请求, 该请求使用 parse_dir_contents() 方法作为回调函数, 用于最终产生我们想要的数据.。
这里展现的即是Scrpay的追踪链接的机制: 当您在回调函数中yield一个Request后, Scrpay将会调度,发送该请求,并且在该请求完成时,调用所注册的回调函数。
基于此方法,您可以根据您所定义的跟进链接的规则,创建复杂的crawler,并且, 根据所访问的页面,提取不同的数据.
一种常见的方法是,回调函数负责提取一些item,查找能跟进的页面的链接, 并且使用相同的回调函数yield一个 Request:
def parse_articles_follow_next_page(self, response):for article in response.xpath("//article"):item = ArticleItem()... extract article data hereyield itemnext_page = response.css("ul.navigation > li.next-page > a::attr('href')")if next_page:url = response.urljoin(next_page[0].extract())yield scrapy.Request(url, self.parse_articles_follow_next_page)
上述代码将创建一个循环,跟进所有下一页的链接,直到找不到为止 – 对于爬取博客、论坛以及其他做了分页的网站十分有效
另一种常见的需求是从多个页面构建item的数据, 这可以使用 在回调函数中传递信息的技巧.
注解
上述代码仅仅作为阐述scrapy机制的样例spider, 想了解 如何实现一个拥有小型的规则引擎(rule engine)的通用spider 来构建您的crawler, 请查看 CrawlSpider
import scrapyclass QuotesSpider(scrapy.Spider):name = "quotes"start_urls = ['http://quotes.toscrape.com/page/1/',]def parse(self, response):for quote in response.css('div.quote'):yield {'text': quote.css('span.text::text').extract_first(),'author': quote.css('small.author::text').extract_first(),'tags': quote.css('div.tags a.tag::text').extract(),}next_page = response.css('li.next a::attr(href)').extract_first()if next_page is not None:next_page = response.urljoin(next_page)yield scrapy.Request(next_page, callback=self.parse)
import scrapyclass AuthorSpider(scrapy.Spider):name = 'author'start_urls = ['http://quotes.toscrape.com/']def parse(self, response):# follow links to author pagesfor href in response.css('.author + a::attr(href)').extract():yield scrapy.Request(response.urljoin(href),callback=self.parse_author)# follow pagination linksnext_page = response.css('li.next a::attr(href)').extract_first()if next_page is not None:next_page = response.urljoin(next_page)yield scrapy.Request(next_page, callback=self.parse)def parse_author(self, response):def extract_with_css(query):return response.css(query).extract_first().strip()yield {'name': extract_with_css('h3.author-title::text'),'birthdate': extract_with_css('.author-born-date::text'),'bio': extract_with_css('.author-description::text'),}
使用Spider参数
当运行爬虫时,可以使用 -a
选项,为爬虫提供命令行参数:
scrapy crawl quotes -o quotes-humor.json -a tag=humor
设置的参数通过 Spider 的 __init__
方法 来变成 的 spider attributes.
这个例子中, 提供给 tag
参数的值 可以通过 self.tag
调用。
import scrapyclass QuotesSpider(scrapy.Spider):name = "quotes"def start_requests(self):url = 'http://quotes.toscrape.com/'tag = getattr(self, 'tag', None)if tag is not None:url = url + 'tag/' + tagyield scrapy.Request(url, self.parse)def parse(self, response):for quote in response.css('div.quote'):yield {'text': quote.css('span.text::text').extract_first(),'author': quote.css('small.author::text').extract_first(),}next_page = response.css('li.next a::attr(href)').extract_first()if next_page is not None:next_page = response.urljoin(next_page)yield scrapy.Request(next_page, self.parse)
如果传递 tag=humor
参数给 spider,就只会访问url的 “humor” 标签, such as http://quotes.toscrape.com/tag/humor
.
更过关于处理spider参数:https://doc.scrapy.org/en/1.3/topics/spiders.html#spiderargs
例 子
学习的最好方法就是参考例子,Scrapy也不例外。Scrapy提供了一个叫做 dirbot 的样例项目供您把玩学习。其包含了在教程中介绍的dmoz spider。您可以通过 https://github.com/scrapy/dirbot 找到 dirbot 。README文件对项目内容进行了详细的介绍。
Scrapy project 另一个名叫 quotesbot 的样例项目, 包含两个 spiders 关于 http://quotes.toscrape.com, 一个是使用 CSS selectors 另一个是使用 XPath expressions.
中间件模板:
# -*- coding: utf-8 -*-# Define here the models for your spider middleware
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/spider-middleware.htmlfrom scrapy import signalsclass SpidercoreSpiderMiddleware(object):# Not all methods need to be defined. If a method is not defined,# scrapy acts as if the spider middleware does not modify the# passed objects.@classmethoddef from_crawler(cls, crawler):# This method is used by Scrapy to create your spiders.s = cls()crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)return sdef process_spider_input(self, response, spider):# Called for each response that goes through the spider# middleware and into the spider.# Should return None or raise an exception.return Nonedef process_spider_output(self, response, result, spider):# Called with the results returned from the Spider, after# it has processed the response.# Must return an iterable of Request, dict or Item objects.for i in result:yield idef process_spider_exception(self, response, exception, spider):# Called when a spider or process_spider_input() method# (from other spider middleware) raises an exception.# Should return either None or an iterable of Response, dict# or Item objects.passdef process_start_requests(self, start_requests, spider):# Called with the start requests of the spider, and works# similarly to the process_spider_output() method, except# that it doesn’t have a response associated.# Must return only requests (not items).for r in start_requests:yield rdef spider_opened(self, spider):spider.logger.info('Spider opened: %s' % spider.name)class SpidercoreDownloaderMiddleware(object):# Not all methods need to be defined. If a method is not defined,# scrapy acts as if the downloader middleware does not modify the# passed objects.@classmethoddef from_crawler(cls, crawler):# This method is used by Scrapy to create your spiders.s = cls()crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)return sdef process_request(self, request, spider):# Called for each request that goes through the downloader# middleware.# Must either:# - return None: continue processing this request# - or return a Response object# - or return a Request object# - or raise IgnoreRequest: process_exception() methods of# installed downloader middleware will be calledreturn Nonedef process_response(self, request, response, spider):# Called with the response returned from the downloader.# Must either;# - return a Response object# - return a Request object# - or raise IgnoreRequestreturn responsedef process_exception(self, request, exception, spider):# Called when a download handler or a process_request()# (from other downloader middleware) raises an exception.# Must either:# - return None: continue processing this exception# - return a Response object: stops process_exception() chain# - return a Request object: stops process_exception() chainpassdef spider_opened(self, spider):spider.logger.info('Spider opened: %s' % spider.name)
用户代理(User-Agent)中间件 示例代码:
# coding: utf-8import random
from scrapy.downloadermiddlewares.useragent import UserAgentMiddlewareclass RandomUserAgentMiddleware(UserAgentMiddleware):agents = ["Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55.0","Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)""Chrome/67.0.3396.62 Safari/537.36","User-Agent,Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0;","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)""Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240"]def __init__(self, user_agent=''):super(RandomUserAgentMiddleware, self).__init__()self.user_agent = user_agentdef _user_agent(self, spider):if hasattr(spider, 'user_agent'):return spider.user_agentelif self.user_agent:return self.user_agentreturn random.choice(self.agents)def process_request(self, request, spider):ua = self._user_agent(spider)if ua:request.headers.setdefault('User-Agent', ua)
然后把中间件加到 ITEM_PIPELINE 设置里面即可
在 Pycharm 中调试 scrapy 爬虫的两种方法
通常,运行scrapy爬虫的方式是在命令行输入scrapy crawl <spider_name>
,调试的常用方式是在命令行输入scrapy shell <url_name>
。总的来说,调试方法比较单一。其实,还有两种调试方法,可以在pycharm中实现调试。
1.使用scrapy.cmdline的execute方法
首先,在项目文件 scrapy.cfg 的同级建立 main.py 文件(注意,必须是同级建立),在其中键入如下代码:
from scrapy import cmdline
import sys
import ossys.path.append(os.path.dirname(os.path.abspath(__file__)))cmdline.execute(['scrapy', 'crawl', 'spider_name']) # 你需要将此处的spider_name替换为你自己的爬虫名称# 或者 cmdline.execute('scrapy crawl spider_name'.split(' '))pass
在其余爬虫文件中设置断点后,运行main.py,即可实现在pycharm中的调试。
2.使用scrapy的CrawlerProcess方法
在项目文件scrapy.cfg的同级建立main.py文件(注意,必须是同级建立),在其中键入如下代码:
from scrapy.crawler import CrawlerProcess
from scrapy.utils.project import get_project_settingsif __name__ == '__main__':process = CrawlerProcess(get_project_settings())process.crawl('spider_name') # 你需要将此处的spider_name替换为你自己的爬虫名称process.start()
在其余爬虫文件中设置断点后,运行main.py,即可实现在pycharm中的调试。
两种方式都很简单实用,值得掌握。