Scrapy新手指南:创建蜘蛛抓取数据
目标:
安装scrapy
并创建蜘蛛程序实现对网站http://quotes.toscrape.com/page/1/
的内容抓取。
安装Scrapy
pip install Scrapy
创建第一个项目
scrapy startproject 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 middlewares.py # project middlewares file pipelines.py # project pipelines file settings.py # project settings file spiders/ # a directory where you'll later put your spiders __init__.py
创建第一只蜘蛛
蜘蛛文件存放于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 = f'quotes-{page}.html' with open(filename, 'wb') as f: f.write(response.body) self.log(f'Saved file {filename}')
这里的 name = "quotes"
参数很重要,是后面命令行启动蜘蛛指定的名称。
如您所见,我们的Spider子类 scrapy.Spider
并定义了一些属性和方法:
name
:标识蜘蛛。它在一个项目中必须是唯一的,即不能为不同的爬行器设置相同的名称。start_requests()
:必须返回请求的可迭代(您可以返回请求列表或编写生成器函数),爬行器将从该请求开始爬行。后续请求将从这些初始请求中相继生成。parse()
:将被调用以处理为每个请求下载的响应的方法。Response参数是 它保存页面内容,并具有进一步有用的方法来处理它。这个
parse()
方法通常解析响应,将抓取的数据提取为字典,还查找要遵循的新URL并创建新请求 (Request
)。
运行蜘蛛
scrapy crawl quotes
输出
... (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
两个文件。
内容分析
在目标网站http://quotes.toscrape.com中,我们想要的那部分内容的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>
我们需要提取 class为text
,author
以及tags
中的内容。
数据抓取
蜘蛛爬行规则的实现
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').get(), 'author': quote.css('small.author::text').get(), 'tags': quote.css('div.tags a.tag::text').getall(), }
这时候运行蜘蛛scrapy crawl quotes
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.”"}
可以看到每条json格式的数据
保存数据
使用-O
参数启动蜘蛛
scrapy crawl quotes -O quotes.json
如果要保存成json lines格式,可使用
scrapy crawl quotes -o quotes.jl
如果您想对爬取的项目执行更复杂的操作,可以编写一个 Item Pipeline . 项目创建时已为您设置了项目管道的占位文件,位于 tutorial/pipelines.py
. 但是,如果只想存储爬取的项目,则不需要实现任何项目管道。
分页的处理
目前我们只提供了两条页面的内容,分别是/page/1/
和/page/2/
,我们首先要找到分页源码
<ul class="pager"> <li class="next"> <a href="/page/2/">Next <span aria-hidden="true">→</span></a> </li> </ul>
再对quotes_spider.py
蜘蛛稍作修改
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').get(), 'author': quote.css('small.author::text').get(), 'tags': quote.css('div.tags a.tag::text').getall(), } next_page = response.css('li.next a::attr(href)').get() if next_page is not None: next_page = response.urljoin(next_page) yield scrapy.Request(next_page, callback=self.parse)
现在,在提取数据之后, parse()
方法查找到下一页的链接,并使用 urljoin()
方法(因为链接可以是相对的),并生成对下一页的新请求,将自身注册为回调,以处理下一页的数据提取,并保持爬行在所有页中进行。
这里您看到的是scrapy的分页链接机制:当您在回调方法中生成一个请求时,scrapy将计划发送该请求,并注册一个回调方法,以便在该请求完成时执行。
使用它,您可以构建复杂的爬虫程序,这些爬虫程序根据您定义的规则跟踪链接,并根据所访问的页面提取不同类型的数据。
在我们的示例代码中,它创建了一种循环,跟踪到下一页的所有链接,直到找不到一个为止——这对于爬行博客、论坛和其他带有分页的站点很方便。
创建请求的快捷方式
你可以使用response.follow
来实现
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').get(), 'author': quote.css('span small::text').get(), 'tags': quote.css('div.tags a.tag::text').getall(), } next_page = response.css('li.next a::attr(href)').get() if next_page is not None: yield response.follow(next_page, callback=self.parse)
不像Scrapy.Request
, response.follow
直接支持相对URL,无需调用urljoin()
。
注意 response.follow
只返回一个请求实例,您仍然需要生成这个请求。
也可以将选择器传递给 response.follow
而不是字符串;此选择器应提取必要的属性:
for href in response.css('ul.pager a::attr(href)'): yield response.follow(href, callback=self.parse)
为了 <a>
元素有一个快捷方式: response.follow
自动使用其href
属性。因此代码可以进一步缩短:
for a in response.css('ul.pager a'): yield response.follow(a, callback=self.parse)
要从iterable创建多个请求,可以使用 response.follow_all
取而代之的是:
anchors = response.css('ul.pager a') yield from response.follow_all(anchors, callback=self.parse)
或者,进一步缩短:
yield from response.follow_all(css='ul.pager a', callback=self.parse)
更多示例
下面是另一个spider,它演示回调和以下链接,这次是为了抓取作者信息:
mport scrapy class AuthorSpider(scrapy.Spider): name = 'author' start_urls = ['http://quotes.toscrape.com/'] def parse(self, response): author_page_links = response.css('.author + a') yield from response.follow_all(author_page_links, self.parse_author) pagination_links = response.css('li.next a') yield from response.follow_all(pagination_links, self.parse) def parse_author(self, response): def extract_with_css(query): return response.css(query).get(default='').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
像我们以前看到的那样回拨。
在这里,我们将回调传递给 response.follow_all
作为位置参数,以使代码更短;它还适用于 Request
。
这个 parse_author
回调定义了一个助手函数,用于从CSS查询中提取和清理数据,并用作者数据生成python dict。
蜘蛛的启动参数
通过使用 -a
运行它们时的选项:
scrapy crawl quotes -O quotes-humor.json -a tag=humor
这些论点被传给蜘蛛 __init__
方法并默认成为spider属性。
在本例中,为 tag
参数将通过 self.tag
. 您可以使用它使您的蜘蛛只获取带有特定标记的引号,并基于以下参数构建URL::
mport scrapy class 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/' + tag yield scrapy.Request(url, self.parse) def parse(self, response): for quote in response.css('div.quote'): yield { 'text': quote.css('span.text::text').get(), 'author': quote.css('small.author::text').get(), } next_page = response.css('li.next a::attr(href)').get() if next_page is not None: yield response.follow(next_page, self.parse)
如果你通过 tag=humor
对于这个蜘蛛,您会注意到它只访问来自 humor
标记,如 http://quotes.toscrape.com/tag/humor
.
本文示例代码来自官网:https://docs.scrapy.org/en/latest/intro/tutorial.html