Scrapy教程

原文地址https://doc.scrapy.org/en/latest/intro/tutorial.html

此教程我们假设你已经装好了Scrapy,如果没有请查看安装指南.。

我们将要抓取 quotes.toscrape.com网站,这个网站展示了很多名人名言。

此教程指导你完成一下任务:

  1. 新建一个Scrapy工程
  2. 编写一个spider爬网站提取数据
  3. 用命令行导出爬取的数据
  4. 改变spider递归爬去链接
  5. 使用spider参数

Scrapy是python编写。如果你是python新手,你可能先要知道这门语言大概是什么样的,才能直到Scrapy的更多东西。

如果你熟悉其他编程语言,想快速学习python,我们建议你使用 Dive Into Python 3或者Python Tutorial

如果你刚接触编程语言想学习python,可以使用Learn Python The Hard Way。或者看this list of Python resources for non-programmers

创建一个项目

在抓取之前,你必须构建一个新的Scrapy project。到你先要存储的目录运行:

scrapy startproject tutorial

这将创建一个含有以下内容的tutorial目录:

tutorial/
scrapy.cfg # deploy configuration file tutorial/ # project's Python module, you'll import your code from here
__init__.py items.py # project items definition file pipelines.py # project pipelines file settings.py # project settings file spiders/ # a directory where you'll later put your spiders
__init__.py

我们第一个爬虫

蜘蛛是你定义的一些去爬取网站信息的类。他们必须继承自scrapy.Spider,定义初始请求,如何选择页面爬取这是可选的,以及如何解析下载页面的内容提取数据。

这是我们第一个蜘蛛的代码,把它保存在tutorial/spiders目录的 quotes_spider.py 中文件。

import scrapy

class 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' % page
with open(filename, 'wb') as f:
f.write(response.body)
self.log('Saved file %s' % filename)

如你所见,我们的蜘蛛继承自 scrapy.Spider 并且定义了一些属性和方法。

  • name:标识这个蜘蛛。在一个项目中必须时唯一的,意味着你不能给不同的蜘蛛设置相同的名称。
  • start_requests():必须返回一个请求的迭代(可以返回一个请求的列表或者写一个生成器函数),蜘蛛从这里开始爬去。子序列请求从这些初始的请求自动生成。
  • parse():在每个请求的相应完成时调用的方法。response参数是TextResponse的一个实例,拥有页面内容和更多有用的函数操作。

parse()函数通常解析响应内容,把抓到的数据提取为dicts随后查找新的URLS创建新的请求。

如何运行我们的蜘蛛

为了让我们的蜘蛛工作,到项目的最顶层目录运行:

scrapy crawl quotes

这条命令运行我们刚添加的名为quotes的蜘蛛。它发送一些请求到quotes.toscrape.com。你将得到如下输出:

... (omitted for brevity)
2016-12-16 21:24:05 [scrapy.core.engine] INFO: Spider opened
2016-12-16 21:24:05 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2016-12-16 21:24:05 [scrapy.extensions.telnet] DEBUG: Telnet console listening on 127.0.0.1:6023
2016-12-16 21:24:05 [scrapy.core.engine] DEBUG: Crawled (404) <GET http://quotes.toscrape.com/robots.txt> (referer: None)
2016-12-16 21:24:05 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://quotes.toscrape.com/page/1/> (referer: None)
2016-12-16 21:24:05 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://quotes.toscrape.com/page/2/> (referer: None)
2016-12-16 21:24:05 [quotes] DEBUG: Saved file quotes-1.html
2016-12-16 21:24:05 [quotes] DEBUG: Saved file quotes-2.html
2016-12-16 21:24:05 [scrapy.core.engine] INFO: Closing spider (finished)
...

现在,检查当前目录。你会注意到创建了两个新文件quotes-1.html 和quotes-2.html,里面包含了urls的响应数据。

提示

如果你奇怪为什么我们还没有解析HTML,淡定,很快会讲到。

内部机制是什么

Scrapy调用蜘蛛的start_requests方法,一旦接收到一个响应,立马初始化Response对象然后调用请求的回掉函数(在此例中,时parse()函数)把response对象作为参数。

start_requests函数简写

作为start_requests函数的替代实现,你可以仅定义一个名为start_urls的urls列表属性。词列表将在默认的start_requests()函数实现中被使用为你的蜘蛛创建出事请求。

import scrapy

class 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' % page
with open(filename, 'wb') as f:
f.write(response.body)

urls的每次请求都将调用parse()函数,即使我们没有显示告诉Scrapy这么做。这是因为parse()是Scrapy在没有显示给回掉函数赋值时的默认回掉函数。

提取数据

最好的学习使用Scrapy的选择器的方式是使用Scrapy shell

scrapy shell 'http://quotes.toscrape.com/page/1/'

提示

记住使用单引号包裹地址否则包含参数(如&字符)将不会工作

在windows中,使用双引号

你将看到:

[ ... Scrapy log here ... ]
2016-09-19 12:09:27 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://quotes.toscrape.com/page/1/> (referer: None)
[s] Available Scrapy objects:
[s] scrapy scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s] crawler <scrapy.crawler.Crawler object at 0x7fa91d888c90>
[s] item {}
[s] request <GET http://quotes.toscrape.com/page/1/>
[s] response <200 http://quotes.toscrape.com/page/1/>
[s] settings <scrapy.settings.Settings object at 0x7fa91d888c10>
[s] spider <DefaultSpider 'default' at 0x7fa91c8af990>
[s] Useful shortcuts:
[s] shelp() Shell help (print this help)
[s] fetch(req_or_url) Fetch request (or URL) and update local objects
[s] view(response) View response in a browser
>>>

使用shell,你可以使用response对象的CSS 函数选择元素。

>>> response.css('title')
[<Selector xpath='descendant-or-self::title' data='<title>Quotes to Scrape</title>'>]

response.css('title')的运行结果是一个名为SelectorList的list-like对象,它是包含XML/HTML元素的 Selector 对象列表允许你进一步查询选择和提取数据。

为了导出title的文本,你可以:

>>> response.css('title::text').extract()
['Quotes to Scrape']

此处有两点要注意:一、我们添加了::text到CSS查询中,意味着我们只选择了<title>玄素的text元素。如果我们不指定::text,我们会得到含有标记的整个title元素。

>>> response.css('title').extract()
['<title>Quotes to Scrape</title>']

二、.extract()调用结果是一个列表,因为我们处理的是SelectorList对象。当你知道你只需要第一个结果时,你可以:

>>> response.css('title::text').extract_first()
'Quotes to Scrape'

作为一种替换方法,你可以这么写:

>>> response.css('title::text')[0].extract()
'Quotes to Scrape'

然而,使用extract()extract_first()方法避免了IndexError并且在没有找到任何匹配元素时返回None

这有个教训,对于大多数抓取代码,你想要在页面不能找到元素时有伸缩性,以至于即使在抓取数据时发生错误,你依然可以得到一些数据。

除了extract()extract_first()方法,你还可以使用re()的正则表达式方法。

>>> response.css('title::text').re(r'Quotes.*')
['Quotes to Scrape']
>>> response.css('title::text').re(r'Q\w+')
['Quotes']
>>> response.css('title::text').re(r'(\w+) to (\w+)')
['Quotes', 'Scrape']

为了找到适当的CSS选择器,你可从shell中使用view(response)浏览响应界面。你可以使用浏览器开发工具或插件如Firebug(此处请看使用Firebug 抓取使用FireFox抓取)。

选择器小工具也是一个查找CSS选择器很好的工具,可以可视化的查找元素,可在很多浏览器中工作。

XPATH:简介

除了css,Scrapy选择器也支持XPath表达式:

>>> response.xpath('//title')
[<Selector xpath='//title' data='<title>Quotes to Scrape</title>'>]
>>> response.xpath('//title/text()').extract_first()
'Quotes to Scrape'

XPATH表达式很强大,是Scrapy选择器的基础。事实上,CSS选择器在内部转换为Xpath。你可以在shell查看文本选择器的对象类型。

尽管不如CSS选择器流行,Xpath表达式却更强大。它除了可以导航到结构也可以查找内容。使用xpath,你能这么选择如:选择包含Next Page的文本连接。这使得xpath非常适合抓取,我们鼓励你学习Xpath,即使你已经知道如何构造CSS选择器,它会更简单。

我们在这不会涉及XPath太多,你可以阅读使用XPath.为了学习Xpath,我们建议通过例子学习XPath教程,和如何使用XPath思考

提取quotes和authors

现在你知道了一点关于选择和提起的知识了,让我们完善我们的spider,写代码从网站页面提取quotes。

http://quotes.toscrape.com中的每个quote的HTML形式类似下面:

<div class="quote">
<span class="text">“The world as we have created it is a process of our
thinking. It cannot be changed without changing our thinking.”</span>
<span>
by <small class="author">Albert Einstein</small>
<a href="/author/Albert-Einstein">(about)</a>
</span>
<div class="tags">
Tags:
<a class="tag" href="/tag/change/page/1/">change</a>
<a class="tag" href="/tag/deep-thoughts/page/1/">deep-thoughts</a>
<a class="tag" href="/tag/thinking/page/1/">thinking</a>
<a class="tag" href="/tag/world/page/1/">world</a>
</div>
</div>

我们打开scrapy shell并做一些解决如何提取我们想要的数据的事。

$ scrapy shell 'http://quotes.toscrape.com'

我们使用下面语法得到一系列的quote元素的选择器:

>>> response.css("div.quote")

查询返回的每个选择器我们还可以查询它们的子元素。我们把第一个选择器赋值给变量,这样我们可以直接运行指定的quote选择器。

quote = response.css("div.quote")[0]

现在我们从quote导出title,authortags使用我们刚创建的quote对象。当你知道你只需要第一个结果时,你可以:

>>> title = quote.css("span.text::text").extract_first()
>>> title
'“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”'
>>> author = quote.css("small.author::text").extract_first()
>>> author
'Albert Einstein'

考虑到标签是字符串列表,我们可以使用.extract()方法获取他们。

>>> tags = quote.css("div.tags a.tag::text").extract()
>>> tags
['change', 'deep-thoughts', 'thinking', 'world']

解决了如何导出每个,我们现在可迭代所有quotes元素把他们保存到Python字典中。

>>> for quote in response.css("div.quote"):
... 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()
... print(dict(text=text, author=author, tags=tags))
{'tags': ['change', 'deep-thoughts', 'thinking', 'world'], 'author': 'Albert Einstein', 'text': '“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”'}
{'tags': ['abilities', 'choices'], 'author': 'J.K. Rowling', 'text': '“It is our choices, Harry, that show what we truly are, far more than our abilities.”'}
... a few more of these, omitted for brevity
>>>

在我们的蜘蛛里导出数据

让我们回到蜘蛛。直到现在,仍然没有导出任何数据,只是把HTML页面保存到本地文件中。我们把导出逻辑集成到spider中。

一个Scrapy蜘蛛通常包含多个页面抓取数据的字典。这样,我们可以使用在回调函数中使用yieldPython关键字,如下所示:

import scrapy

class 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('span small::text').extract_first(),
'tags': quote.css('div.tags a.tag::text').extract(),
}

如果你运行这个蜘蛛,它把导出数据输出到日志中:

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.”"}

保存抓取到的数据

最简单的保存抓取数据是使用Feed exports, 使用下面的命令行:

scrapy crawl quotes -o quotes.json

这将生成一个quotes.json文件包含所有抓取像序列化为json。

由于历史原因,Scrapy使用追加而不是覆盖,如果你运行两次此命令而没有在第二次删除之前的文件,你将得到一个损毁的JSON文件。

你也可以使用其他格式,如Json Lines

scrapy crawl quotes -o quotes.jl

Json Lines格式很有用,因为她是stream-like。你可以往里面轻松的添加新纪录。他没有上面的JSON文件的问题当你运行两次的时候。同时,因为每条记录是一行,你可以处理超大文件而不必担心内存问题,有很多工具如JQ可在命令行处理。

在小项目里(例如此教程),这样就够了。然而,如果你想处理更复杂的抓取项,你可以编写[Item 管道]。当创建项目的时候,会在tutorial/pipelines.py构建一个Item 管道文件。这样如果你只是想保存抓取到的项,就不需要实现任何的Item管道。

下面的连接

假如你不想仅抓取http://quotes.toscrape.com网站中的两个页面,而是想抓取所有的网站页面。

现在你知道如何从页面抓取数据,让我们看看下面的连接如何得到。

首先从页面中提取我们想要的连接。查看我们的页面,我们可以看见页面中的下一页连接如下所示标志:

<ul class="pager">
<li class="next">
<a href="/page/2/">Next <span aria-hidden="true">→</span></a>
</li>
</ul>

试着在shell中提取它:

>>> response.css('li.next a').extract_first()
'<a href="/page/2/">Next <span aria-hidden="true">→</span></a>'

这得到了整个anchor元素,但是我们想要href属性。为了如此,Scrapu提供了CSS的扩展使你可以选择属性内容,如下:

>>> response.css('li.next a::attr(href)').extract_first()
'/page/2/'

现在我们的spider被改成了可以跟踪下一页从中导出数据:

import scrapy

class 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('span small::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)

现在,导出数据后,parse()函数查找下一页,使用urljoin构建一个绝对路径URL并生成一个到下一页的新请求,把下一页的请求注册为回调使得蜘蛛可以爬到所有的页面。

这是Scrapy跟踪页面的机制:当你在回调中生成一个请求对象,Scrapy会安排请求发送并注册回调函数在请求结束时运行。

使用这些,你可以构建复杂的爬虫系统,链接规则可以自定义,根据访问页面导出各种各样的数据。

在我们的例子中,它创建了一系列循环跟踪所有的链接到下一页直到找不到任何连接——方便爬取博客,论坛或其他的导航网站。

更多示例和模式

这是另一个蜘蛛用来解释回调和跟踪连接,这次抓取作者信息:

import scrapy

class AuthorSpider(scrapy.Spider):
name = 'author' start_urls = ['http://quotes.toscrape.com/'] def parse(self, response):
# follow links to author pages
for href in response.css('.author+a::attr(href)').extract():
yield scrapy.Request(response.urljoin(href),
callback=self.parse_author) # follow pagination links
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) 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'),
}

蜘蛛从主页面开始,使用parse_author回调函数跟踪所有的作者页面连接,同时用parse回调函数跟踪导航连接如我们之前看到的。

parse_author回调函数定义了一个帮助方法,从CSS查询提取和清理并使用作者数据生成Python dict。

另一件关于蜘蛛的有趣的事情是,即使有很多名言出自同一作者,我们也不必担心多次访问相同作者的页面。默认情况下,Scrapy过滤掉重复的已访问的请求地址,避免程序太多次点击服务器的问题。这是用DUPEFILTER_CLASS配置。

希望你已理解了Scrapy如何跟踪页面和回调的机制。

这个程序利用跟踪链接机制实现,查看CrawlSpider类,它是一个通用的蜘蛛实现了一个小的规则引擎,你可以在这之上编写自己的爬虫。

Scrapy教程的更多相关文章

  1. Miniconda安装scrapy教程

    一.背景说明 前两天想重新研究下Scrapy,当时的环境是PyCharm社区版+Python 3.7.使用pip安装一直报错 “distutils.errors.DistutilsPlatformEr ...

  2. Python爬虫框架Scrapy教程(1)—入门

    最近实验室的项目中有一个需求是这样的,需要爬取若干个(数目不小)网站发布的文章元数据(标题.时间.正文等).问题是这些网站都很老旧和小众,当然也不可能遵守 Microdata 这类标准.这时候所有网页 ...

  3. Scrapy 1.4 文档 03 Scrapy 教程

    在本教程中,我们假设您已经安装了Scrapy.如果没有,请参阅安装指南. 我们将要抓取 quotes.toscrape.com,一个列出著名作家的名言(quote)的网站. 本教程将引导您完成以下任务 ...

  4. scrapy 教程

    pip install Scrapy 结果报错,那就一个一个安装吧 一.安装  1.pip install wheel 后面的都一样 pip install lxml pip install pyop ...

  5. Scrapy 教程(七)-架构与中间件

    Scrapy 使用 Twisted 这个异步框架来处理网络通信,架构清晰,并且包含了各种中间件接口,可以灵活的完成各种需求. Scrapy 架构 其实之前的教程都有涉及,这里再做个系统介绍 Engin ...

  6. Scrapy 教程(六)-反爬

    伪装浏览器 服务器可以查看访问的终端,如果不是浏览器,可能会被屏蔽,而且即使你用同一浏览器访问频率过快,也可能被屏蔽,所以需要伪装浏览器反爬. 有以下几种方法 1. 在 settings中添加 use ...

  7. Scrapy 教程(三)-网站解析

    有经验的人都知道,解析网站需要尝试,看看得到的数据是不是想要的,那么在scrapy中怎么尝试呢? 调试工具-shell 主要用于编写解析器 命令行进入shell scrapy shell url 这个 ...

  8. Scrapy 教程(二)-操作流程

    scrapy 是个爬虫框架,是由python编写的,用法类似 django 框架. 创建工程 在开始爬虫之前,先创建工程 scrapy startproject projectname 目录结构如下图 ...

  9. Scrapy教程--豆瓣电影图片爬取

    一.先上效果 二.安装Scrapy和使用 官方网址:https://scrapy.org/. 安装命令:pip install Scrapy 安装完成,使用默认模板新建一个项目,命令:scrapy s ...

随机推荐

  1. pat 甲级 1078. Hashing (25)

    1078. Hashing (25) 时间限制 100 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Yue The task of t ...

  2. Codevs 1159 最大全0子矩阵 悬线法!!!!

    1159 最大全0子矩阵 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题目描述 Description 在一个0,1方阵中找出其中最大的全0子矩阵,所谓最大是指O ...

  3. 不用框架使用ajax 纯js使用ajax post,get范例及其区别

    原文发布时间为:2009-11-15 -- 来源于本人的百度文章 [由搬家工具导入] 不用框架使用ajax 纯js使用ajax post,get范例及其区别 ===================== ...

  4. Using MEF to Set Up Dependency Injection

    The Managed Extensibility Framework (MEF) is a built-in set of elements that allows you to “export” ...

  5. LeetCode OJ-- First Missing Positive

    https://oj.leetcode.com/problems/first-missing-positive/ 给一列数,找出缺失的第一个正数.要求时间复杂度 O(n) 第一步遍历一遍,找出最大的数 ...

  6. 对动态规划(Dynamic Programming)的理解:从穷举开始(转)

    转自:http://janfan.cn/chinese/2015/01/21/dynamic-programming.html 动态规划(Dynamic Programming,以下简称dp)是算法设 ...

  7. HDU 4870 Rating (高斯消元)

    题目链接  2014 多校1 Problem J 题意  现在有两个账号,初始$rating$都为$0$,现在每次打分比较低的那个,如果进前$200$那么就涨$50$分,否则跌$100$分.   每一 ...

  8. Jmeter脚本两种录制方式

    Jmeter 是一个非常流行的性能测试工具,虽然与LoadRunner相比有很多不足,比如:它结果分析能力没有LoadRunner详细:很它的优点也有很多:   l       开源,他是一款开源的免 ...

  9. Hdoj 2509 Be the Winner

    Diciption Let's consider m apples divided into n groups. Each group contains no more than 100 apples ...

  10. [BZOJ3212][POJ3468]A Simple Problem with Integers

    题目大意: 维护一个长度为$n(n\leq100000)$的数列,支持区间加.区间求和两种操作,操作共$m(m\leq100000)$次. 思路: Splay区间操作. #include<cst ...