标签:des 相同 操作 star for 例子 ems .com 表达式
在本教程中,我们假设您已经安装了Scrapy。如果没有,请参阅安装指南。
我们将要抓取 quotes.toscrape.com,一个列出著名作家的名言(quote)的网站。
本教程将引导您完成以下任务:
Scrapy 是用 Python 编写的。如果你没学过 Python,你可能需要了解一下这个语言,以充分利用 Scrapy。
如果您已经熟悉其他语言,并希望快速学习 Python,我们建议您阅读 Dive Into Python 3。或者,您可以学习 Python 教程。
如果您刚开始编程,并希望从 Python 开始,在线电子书《Learn Python The Hard Way》非常有用。您也可以查看非程序员的 Python 资源列表。
在开始抓取之前,您必须创建一个新的 Scrapy 项目。 进入您要存储代码的目录,然后运行:
scrapy startproject tutorial
这将创建一个包含以下内容的 tutorial 目录:
tutorial/ scrapy.cfg # 项目配置文件 tutorial/ # 项目的 Python 模块,放置您的代码的地方 __init__.py items.py # 项目项(item)定义文件 pipelines.py # 项目管道(piplines)文件 settings.py # 项目设置文件 spiders/ # 一个你以后会放置 spider 的目录 __init__.py
Spider 是您定义的类,Scrapy 用它从网站(或一组网站)中抓取信息。 他们必须是 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)
你可以看到,我们的 Spider 是 scrapy.Spider 的子类并定义了一些属性和方法:
parse() 方法通常解析响应,将抓取的数据提取为字典,并且还可以查找新的 URL 来跟踪并从中创建新的请求(Request)。
要使我们的爬虫工作,请进入项目的根目录并运行:
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,其中包含各个URL的内容,就像我们的 parse 方法指示一样。
注意
如果您想知道为什么我们还没有解析 HTML,请继续,我们将尽快介绍。
Spider 的 start_requests 方法返回 scrapy.Request 对象,Scrapy 对其发起请求 。然后将收到的响应实例化为 Response 对象,以响应为参数调用请求对象中定义的回调方法(在这里为 parse 方法)。
用于代替实现一个从 URL 生成 scrapy.Request 对象的 start_requests() 方法,您可以用 URL 列表定义一个 start_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)
Scrapy 将调用 parse() 方法来处理每个 URL 的请求,即使我们没有明确告诉 Scrapy 这样做。 这是因为 parse() 是 Scrapy 的默认回调方法,没有明确分配回调方法的请求默认调用此方法。
学习如何使用 Scrapy 提取数据的最佳方式是在 Scrapy shell 中尝试一下选择器。 运行:
scrapy shell ‘http://quotes.toscrape.com/page/1/‘
注意
在从命令行运行 Scrapy shell 时必须给 url 加上引号,否则包含参数(例如 &符号)的 url 将不起作用。
在Windows上,要使用双引号:
scrapy shell "http://quotes.toscrape.com/page/1/"
你将会看到:
[ ... 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,您可以尝试使用 CSS 选择器选择元素:
>>> response.css(‘title‘) [<Selector xpath=‘descendant-or-self::title‘ data=‘<title>Quotes to Scrape</title>‘>]
运行 response.css(‘title‘) 返回的结果是一个 SelectorList 类列表对象,它是一个指向 XML/HTML 元素的 Selector 对象的列表,允许您进行进一步的查询来细分选择或提取数据。
要从上面的 title 中提取文本,您可以执行以下操作:
>>> response.css(‘title::text‘).extract() [‘Quotes to Scrape‘]
这里有两件事情要注意:一个是我们在 CSS 查询中添加了 ::text,这意味着我们只想要 <title> 元素中的文本。 如果我们不指定 ::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_first() 返回 None,避免了 IndexError
这里有一个教训:对于大多数爬虫代码,您希望它具有容错性,如果在页面上找不到指定的元素导致无法获取某些项,至少其它的数据可以被抓取。
除了 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 选择器语句,您可以在浏览器中打开页面并查看源代码。 您也可以使用浏览器的开发工具或扩展(如 Firebug)(请参阅有关 Using Firebug for scraping 和 Using Firefox for scraping 的部分)。
Selector Gadget 也是一个很好的工具,可以快速找到元素的 CSS 选择器语句,它可以在许多浏览器中运行。
除了 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。
虽然也许不像 CSS 选择器那么受欢迎,但 XPath 表达式提供更多的功能,因为除了导航结构之外,它还可以查看内容。 使用 XPath,您可以选择以下内容:包含文本“下一页”的链接。 这使得 XPath 非常适合抓取任务,我们鼓励您学习 XPath,即使您已经知道如何使用 CSS 选择器,这会使抓取更容易。
我们不会在这里讲太多关于 XPath 的内容,但您可以阅读 using XPath with Scrapy Selectors 获取更多有关 XPath 的信息。 我们推荐教程 to learn XPath through examples,和教程 “how to think in XPath”。
现在你知道了如何选择和提取,让我们来完成我们的爬虫,编写代码从网页中提取名言(quote)。
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>
让我们打开 scrapy shell 玩一玩,找到提取我们想要的数据的方法:
$ scrapy shell ‘http://quotes.toscrape.com‘
得到 quote 元素的 selector 列表:
>>> response.css("div.quote")
通过上述查询返回的每个 selector 允许我们对其子元素运行进一步的查询。 让我们将第一个 selector 分配给一个变量,以便我们可以直接在特定的 quote 上运行我们的 CSS 选择器:
>>> quote = response.css("div.quote")[0]
现在,我们使用刚刚创建的 quote 对象,从该 quote 中提取 title,author 和 tags:
>>> 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‘]
现在已经弄清楚了如何提取每一个信息,接下来遍历所有 quote 元素,并把它们放在一个 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 页面保存到本地文件。 让我们将上述提取逻辑整合到我们的爬虫中。
Scrapy 爬虫通常生成许多包含提取到的数据的字典。 为此,我们在回调方法中使用 yield Python 关键字,如下所示:
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(‘small.author::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 行(JSON Lines):
scrapy crawl quotes -o quotes.jl
JSON 行格式很有用,因为它像流一样,您可以轻松地将新记录附加到文件。 当运行两次时,它不会发生 JSON 那样的问题。 另外,由于每条记录都是单独的行,所以您在处理大文件时无需将所有内容放到内存中,还有 JQ 等工具可以帮助您在命令行中执行此操作。
在小项目(如本教程中的一个)中,这应该是足够的。 但是,如果要使用已抓取的项目执行更复杂的操作,则可以编写项目管道(Item Pipeline)。 在工程的创建过程中已经为您创建了项目管道的占位符文件 tutorial/pipelines.py, 虽然您只需要存储已抓取的项目,不需要任何项目管道。
或许你希望获取网站所有页面的 quotes,而不是从 http://quotes.toscrape.com 的前两页抓取。
现在您已经知道如何从页面中提取数据,我们来看看如何跟踪链接。
首先是提取我们想要跟踪的页面的链接。 检查我们的页面,我们可以看到链接到下一个页面的URL在下面的元素中:
<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>‘
这得到了超链接元素,但是我们需要其属性 href。 为此,Scrapy 支持 CSS 扩展,您可以选择属性内容,如下所示:
>>> response.css(‘li.next a::attr(href)‘).extract_first() ‘/page/2/‘
现在修改我们的爬虫,改为递归地跟踪下一页的链接,从中提取数据:
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(‘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)
现在,在提取数据之后,parse() 方法查找到下一页的链接,使用 urljoin() 方法构建一个完整的绝对 URL(因为链接可以是相对的),并生成(yield)一个到下一页的新的请求, 其中包括回调方法(parse)。
您在这里看到的是 Scrapy 的链接跟踪机制:当您在一个回调方法中生成(yield)请求(request)时,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‘).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: yield response.follow(next_page, callback=self.parse)
不像 scrapy.Request,response.follow 支持相对 URL - 不需要调用urljoin。请注意,response.follow 只是返回一个 Request 实例,您仍然需要生成请求(yield request)。
您也可以将选择器传递给 response.follow,该选择器应该提取必要的属性:
for href in response.css(‘li.next a::attr(href)‘): yield response.follow(href, callback=self.parse)
对于<a>元素,有一个快捷方式:response.follow 自动使用它们的 href 属性。 所以代码可以进一步缩短:
for a in response.css(‘li.next a‘): yield response.follow(a, callback=self.parse)
注意
response.follow(response.css(‘li.next a‘)) 无效,因为 response.css 返回的是一个类似列表的对象,其中包含所有结果的选择器,而不是单个选择器。for 循环或者 response.follow(response.css(‘li.next a‘)[0]) 则可以正常工作。
这是另外一个爬虫,示例了回调和跟踪链接,这次是为了抓取作者信息:
import scrapy class AuthorSpider(scrapy.Spider): name = ‘author‘ start_urls = [‘http://quotes.toscrape.com/‘] def parse(self, response): # 链接到作者页面 for href in response.css(‘.author + a::attr(href)‘): yield response.follow(href, self.parse_author) # 链接到下一页 for href in response.css(‘li.next a::attr(href)‘): yield response.follow(href, 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 回调方法跟踪其它页面。
这里我们将回调方法作为参数直接传递给 response.follow,这样代码更短,也可以传递给 scrapy.Request。
parse_author 回调方法里定义了另外一个函数来根据 CSS 查询语句(query)来提取数据,然后生成包含作者数据的 Python 字典。
这个爬虫演示的另一个有趣的事是,即使同一作者有许多名言,我们也不用担心多次访问同一作者的页面。默认情况下,Scrapy 会将重复的请求过滤出来,避免了由于编程错误而导致的重复服务器的问题。可以通过 DUPEFILTER_CLASS 进行相关的设置。
希望现在您已经了解了 Scrapy 的跟踪链接和回调方法机制。
CrawlSpider 类是一个小规模的通用爬虫引擎,只需要修改其跟踪链接的机制等,就可以在它之上实现你自己的爬虫程序。
另外,一个常见的模式是从多个页面据构建一个包含数据的项(item),有一个将附加数据传递给回调方法的技巧。
在运行爬虫时,可以通过 -a 选项为您的爬虫提供命令行参数:
scrapy crawl quotes -o quotes-humor.json -a tag=humor
默认情况下,这些参数将传递给 Spider 的 __init__ 方法并成为爬虫的属性。
在此示例中,通过 self.tag 获取命令行中参数 tag 的值。您可以根据命令行参数构建 URL,使您的爬虫只爬取特点标签的名言:
import 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‘).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: yield response.follow(next_page, self.parse)
如果您将 tag = humor 传递给爬虫,您会注意到它只会访问标签为 humor 的 URL,例如 http://quotes.toscrape.com/tag/humor。您可以在这里了解更多关于爬虫参数的信息。
本教程仅涵盖了 Scrapy 的基础知识,还有很多其他功能未在此提及。 查看初窥 Scrapy 中的“还有什么?”部分可以快速了解有哪些重要的内容。
您可以通过目录了解更多有关命令行工具、爬虫、选择器以及本教程未涵盖的其他内容的信息。下一章是示例项目。
标签:des 相同 操作 star for 例子 ems .com 表达式
原文地址:http://www.cnblogs.com/-E6-/p/7213872.html