scrapy1.4.0文档.
scrapy startproject tutorial #项目名
#其中包含
tutorial/
scrapy.cfg # 部署配置文件
tutorial/ # project's Python module, you'll import your code from here
__init__.py
items.py # 项目项定义文件
pipelines.py # project pipelines file
settings.py # 项目设置文件
spiders/ # a directory where you'll later put your spiders存放你的爬虫的目录
__init__.py
这是我们第一个蜘蛛的代码。将它保存在一个文件名为 quotes_spider.py下tutorial/spiders在项目目录:
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 并定义了一些属性和方法:
name:识别蜘蛛。它在项目中必须是唯一的,也就是说,您不能为不同的Spiders设置相同的名称。
start_requests():必须返回一个可迭代的请求(您可以返回一个请求列表或写一个生成器函数),Spider将开始爬行。随后的请求将从这些初始请求连续生成。
parse():一种将被调用来处理为每个请求下载的响应的方法。response参数是一个TextResponse保存页面内容的实例,并且还有其他有用的方法来处理它。
该parse()方法通常解析响应,将刮取的数据提取为示例,并且还可以查找新的URL以从中创建新的请求(Request)。
要使我们的蜘蛛工作,请转到项目的顶级目录并运行:
scrapy crawl quotes
现在,检查当前目录下的文件。您应该注意到,已经创建了两个新文件:quotes-1.html和quotes-2.html,其中包含各个URL的内容,正如我们的parse方法指示
什么发生在引擎盖下?
Scrapy计划scrapy.Request通过start_requests蜘蛛方法返回的对象。在收到每个响应后,它实例化Response对象并调用与parse响应作为参数传递的请求相关联的回调方法(在这种情况下,该 方法)。
start_requests方法的快捷方式
您可以使用URL列表来定义一个类属性,而不是实现从URL start_requests()生成scrapy.Request对象的方法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)
该parse()方法将被调用来处理这些URL的每个请求,即使我们没有明确地告诉Scrapy这样做。发生这种情况是因为parse()Scrapy的默认回调方法,对于没有明确分配的回调的请求而言。
学习如何使用Scrapy提取数据的最佳方式是尝试使用Shell Scrapy shell的选择器。运行
scrapy shell "http://quotes.toscrape.com/page/1/"
记住在从命令行运行Scrapy shell时始终将引号括起来,否则包含参数(即&字符)的url 将不起作用。
在Windows上,使用双引号
你会看到像
[ ... Scrapy log here ... ]
2016-09-19 12:09:27 [scrapy.core.engine] DEBUG: Crawled (200) 1/> (referer: None)
[s] Available Scrapy objects:
[s] scrapy scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s] crawler 0x7fa91d888c90>
[s] item {}
[s] request 1/>
[s] response <200 http://quotes.toscrape.com/page/1/>
[s] settings 0x7fa91d888c10>
[s] spider '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')
['descendant-or-self::title' data='Quotes to Scrape '>]
运行的结果response.css(‘title’)是一个名为list的对象 SelectorList,它表示包含SelectorXML / HTML元素的对象列表, 并允许您运行进一步的查询来细分选择或提取数据。
要从上面的标题中提取文本,您可以执行以下操作:
>>> response.css('title::text').extract()
['Quotes to Scrape']
这里有两件事要注意:一个是我们已经添加::text到CSS查询中,这意味着我们只想直接在元素内部选择文本元素 。如果我们没有指定::text,我们将获得完整的标题元素,包括其标签
>>> response.css('title').extract()
[' Quotes to Scrape']
另一件事是调用的结果.extract()是一个列表,因为我们正在处理一个实例SelectorList。当你知道你只是想要第一个结果,在这种情况下,你可以做:
>>> response.css('title::text').extract_first()
'Quotes to Scrape'
或者你可以写
>>> response.css('title::text')[0].extract()
'Quotes to Scrape'
但是,当没有找到与选择匹配的元素时,.extract_first()避免使用IndexError并返回 None。
这里有一个教训:对于大多数scarapy代码,您希望它能够因为页面上找不到的错误而具有弹性,因此即使某些部件无法被刮除,您至少可以获取一些数据。
除了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选择器,您可能会发现使用Web浏览器中的shell打开响应页面view(response)。您可以使用浏览器开发工具或扩展名(如Firebug)(有关使用Firebug进行刮取和使用Firefox进行刮取的部分)。
选择器小工具也是一个很好的工具,可以快速找到可选的元素的CSS选择器,它可以在许多浏览器中运行。
除了CSS,Scrapy选择器还支持使用XPath表达式:
>>> response.xpath('//title')
['//title' data='Quotes to Scrape '>]
>>> response.xpath('//title/text()').extract_first()
'Quotes to Scrape'
XPath表达式非常强大,是Scrapy选择器的基础。实际上,CSS选择器被转换为XPath。您可以看到,如果仔细阅读shell中的选择器对象的文本表示。
现在你知道一些关于选择和提取的东西,我们来完成我们的蜘蛛,编写代码以从网页中提取引号。
http://quotes.toscrape.com中的每个报价都由HTML元素表示,如下所示:
<div class="quote">
"text">“The world as we have created it is a process of our
thinking. It cannot be changed without changing our thinking.”
by "author">Albert Einstein
<a href="/author/Albert-Einstein">(about)a>
<div class="tags">
Tags:
<a class="tag" href="/tag/change/page/1/">changea>
<a class="tag" href="/tag/deep-thoughts/page/1/">deep-thoughtsa>
<a class="tag" href="/tag/thinking/page/1/">thinkinga>
<a class="tag" href="/tag/world/page/1/">worlda>
div>
div>
找出如何提取我们想要的数据:
$ scrapy shell 'http://quotes.toscrape.com'
我们得到一个列表的选择器的报价HTML元素:
>>> response.css("div.quote")
通过上面的查询返回的每个选择器都允许我们对其子元素运行进一步的查询。让我们将第一个选择器分配给一个变量,以便我们可以直接在特定的引用上运行我们的CSS选择器:
>>> quote = response.css("div.quote")[0]
现在,让我们来提取title,author而tags从报价使用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']
已经弄清楚如何提取每一个位,我们现在可以遍历所有引号元素,并把它们放在一个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页面保存到本地文件。我们将上面的提取逻辑整合到我们的蜘蛛中。
Scrape蜘蛛通常生成包含从页面提取的数据的许多字典。为此,我们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的数据:
scrapy crawl quotes -o quotes.json
这将生成一个quotes.json包含所有Scrat项目的文件,并以JSON序列化。
由于历史原因,Scrapy附加到给定的文件,而不是覆盖其内容。如果您在第二次之前删除该文件两次运行此命令,那么最终会出现一个破坏的JSON文件。
您还可以使用其他格式,如JSON行:
scrapy crawl quotes -o quotes.jl
该JSON行格式是有用的,因为它的流状,你可以很容易地新记录追加到它。当运行两次时,它没有相同的JSON问题。另外,由于每条记录都是单独的行,所以您可以处理大文件,而无需将内存中的所有内容都放在一起,还有JQ等工具可以帮助您在命令行中执行此操作。
在小项目(如本教程中的一个)中,这应该是足够的。但是,如果要使用已刮取的项目执行更复杂的操作,则可以编写Item Pipeline。在项目创建时,您已经为您设置了项目管道的占位符文件 tutorial/pipelines.py。虽然您只需要存储已刮取的项目,但不需要实施任何项目管道。
我们来说,而不是从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>'
这获得了锚点元素,但是我们需要该属性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 (由于链接可以是相对的),并且向下一页产生一个新的请求,将其注册为回调以处理下一页的数据提取,并保持浏览所有页面。
您在这里看到的是Scrapy的以下链接机制:当您以回调方式生成请求时,Scrapy将安排该请求发送,并注册一个回调方法,以在该请求完成时执行。
使用它,您可以根据您定义的规则构建复杂的跟踪链接,并根据访问页面提取不同类型的数据。
在我们的示例中,它创建一个循环,跟随到所有到下一页的链接,直到它找不到一个方便的抓取博客,论坛和其他站点分页。
作为创建Request对象的快捷方式,您可以使用 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实例; 您仍然必须提供此请求。
您也可以传递选择器response.follow而不是字符串; 该选择器应该提取必要的属性:
for href in response.css('li.next a::attr(href)'):
yield response.follow(href, callback=self.parse)
对于元素,有一个快捷方式: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):
# follow links to author pages
for href in response.css('.author + a::attr(href)'):
yield response.follow(href, self.parse_author)
# follow pagination links
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查询提取和清理数据,并产生了Python字典与作者的数据。
这个蜘蛛演示的另一个有趣的事情是,即使同一作者有许多引号,我们也不用担心多次访问同一作者页面。默认情况下,Scrapy会将重复的请求过滤出已访问的URL,避免了由于编程错误导致服务器太多的问题。这可以通过设置进行配置 DUPEFILTER_CLASS。
希望现在您对Scrapy的使用以下链接和回调的机制有很好的了解。
作为利用以下链接机制的蜘蛛蜘蛛,请查看CrawlSpider一个通用蜘蛛的类,实现一个小型规则引擎,您可以使用它来将爬虫编写在其上。
另外,一个常见的模式是用多个页面的数据构建一个项目,使用一个技巧将附加数据传递给回调。
您可以通过-a 在运行它们时使用该选项为您的蜘蛛提供命令行参数:
scrapy crawl quotes -o quotes-humor.json -a tag=humor
init默认情况下,这些参数传递给Spider的方法并成为spider属性。
在这个例子中,为参数提供的值tag将通过self.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