在上一篇博文中,我们学会了Scrapy的简单使用(scrapy的安装、爬取网页、提取数据),这一篇我们将继续对官方文档进行学习。
如何从html中提取链接?爬取的网站与上一博文一致,仍然是 http://quotes.toscrape.com。
第一步,从页面中提取出需要的链接。
分析我们的页面,我们可以看到下一页的链接带有以下标记:
<ul class="pager">
<li class="next">
<a href="/page/2/">Next <span aria-hidden="true">→span>a>
li>
ul>
尝试在 scrapy shell 中抽取这一数据。
# 通过上面的CSS选择器,我们提取到了页面中的这个标签,但是我们需要的是这个标签中的href属性
>>> response.css('li.next a').get()
'Next '
# 通过使用attra(href)获得标签中的指定属性
>>> response.css('li.next a::attr(href)').get()
'/page/2/'
# .attrib也能获得指定的属性
>>> response.css('li.next a').attrib['href']
'/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').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() 方法构建完整的绝对URL(因为链接可以是相对的)yield 爬取下一页的新 request ,然后在这个新的 request 中又调用了自身,通过这种递归的方式保证爬取到所有页面。
这就是 Scrapy 中追踪链接的机制,在回调函数中 yield (生成) 了一个新的request,当这个新的 request 完成时,scrapy 又会为这个request 调用回调函数。
创建 request 的快捷方式就是使用 response.fllow()。
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() 返回的是Request 的一个实例,我们仍然可以 yield 这个实例对象。
我们还可以将选择器传递给 response.follow 而不是字符串,然后可以从这个选择器中提取需要的属性:
# response.css() 的返回值是一个选择器的list
for href in response.css('li.next a::attr(href)'):
# href 是一个选择器
yield response.follow(href, callback=self.parse)
对于 < a > 标签而言,response.css() 的默认返回值就是其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() 的返回值是一个选择器的list,而不是单个的选择器对象。
下面的代码将会从 http://quotes.toscrape.com/ 这个url开始,跟踪页面中的作者链接和下一页链接,然后分别调用回调函数parse_author() 和 parse() 处理响应。
import scrapy
class AuthorSpider(scrapy.Spider):
name = 'author'
start_urls = ['http://quotes.toscrape.com/']
def parse(self, response):
# follow links to author pages
# 跟踪作者页面的链接,然后调用parse_author() 回调
for href in response.css('.author + a::attr(href)'):
yield response.follow(href, self.parse_author)
# follow pagination links
# 跟踪下一页的链接,然后调用parse() 回调
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).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'),
}
需要注意的是,在 scrapy 中,同一个 url 并不会爬取多次,这是因为 scrapy 默认会过滤掉那些已经爬取过的 url
,避免由于编程逻辑问题造成服务器访问过多的问题,通过 DUPEFILTER_CLASS 参数来配置。
请点击这里查看 官方例子 quotesbot
默认情况下,在命令行中 -a 参数 会将这些参数传递给 Spider 的 __init__
方法,然后成为了 spider 的属性。
scrapy crawl quotes -o quotes-humor.json -a tag=humor
在代码中可以通过 self.tag 获得命令行中的 tag 的值。
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
# 此时 url 的的值是 http://quotes.toscrape.com/tag/humor
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)
[1] scrapy 官方文档
[2] 爬虫框架Scrapy的安装与基本使用 - 简书