问题和需求
在爬取数据的过程中可能会遇到这种情况:假如你爬取的数据分别是 引言 、 作者 和 作者的出生日期 ,其中前面两个出现在同一个页面上,可以同步爬取,但是第三条数据却需要点入作者下面的链接才能获取,也就是说这三条数据无法同步获取,需要先获取一部分,然后再进入另一个界面获取另一部分。需求是明确的:将第一层界面的数据传输到第二层界面的响应内容中。 为了满足这个需求,我们需要借助 meta
.
网页源代码中的 meta
一般来讲 mete
中记录的是网页的源数据,比如字符的编码格式等等,如果我们打开一个网页的源代码很容易就可以找到 meta
的踪迹,比如打开 Strackoverflow 首页的源代码可以看到 meta
信息如下:
<meta name="description" content="Stack Overflow is the largest, most trusted online community for developers to learn, share​ ​their programming ​knowledge, and build their careers."/>
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, minimum-scale=1.0">
<meta name="twitter:description" property="og:description" itemprop="description" content="Stack Overflow | The World’s Largest Online Community for Developers" />
meta 的应用场景
我们是否会用到 meta
,取决于我们想要爬取的目标网页的数据所在位置,如果数据位于同一层页面,也就是说如果我们对服务器发送请求,我们获得的响应中会同时包含这些我想要的数据,这时候就不需要使用 meta
,另一种情况是,数据不再同一层页面,有的数据在第一层(也就是我们首先看到的页面),有的数据在第二层(也就是需要通过点击进一步请求的页面),这种情况伴随这次请求,每次请求获得的相应数据中分布着我们想要的不同的数据,在这种情况下我们需要将第一次请求获得的目标数据传递到第二次请求获得的相应里,让这些数据成为第二次请求获得的响应的一部分,这样所有的数据都在同一个响应内容里了,这样我们就可以同步提取所有我们想要的数据。这是只有两层的情况,更多层的情况类似。
meta
是scrapy
库中 Request
类的其中一个字典格式的参数,
# 这里就是用来发出请求的 Request 类,
class Request(object_ref):
# 省略部分内容
def __init__(
self,
url: str,
callback: Optional[Callable] = None,
method: str = "GET",
headers: Optional[dict] = None,
body: Optional[Union[bytes, str]] = None,
cookies: Optional[Union[dict, List[dict]]] = None,
# meta 在这里,可以看到它被指定为字典格式。
meta: Optional[dict] = None,
encoding: str = "utf-8",
priority: int = 0,
dont_filter: bool = False,
errback: Optional[Callable] = None,
flags: Optional[List[str]] = None,
cb_kwargs: Optional[dict] = None,
)
# 省略部分内容
# 我们常见的用法是 yield Request(url=full_link, meta={'k1': item['quote'], 'k2': item['author']}, callback=self.parse_date)
实例
明确需求
我要爬取的网站是 Quotes to Scrapy ,这是专门为练习爬虫设立的网站。我需要的数据类型是第一页的引言记作 ‘quote’ 、作者记作 ‘author’ 和点击链接后获得的界面的作者出生日期,记作 ‘date’,需要的数据量是 一页 数据。
网页分析
quote 和 author 在第一层,date 在第二层,第一层页面中一共有 10 个数据块(一共有10个引言),我们用 for 循环遍历每个数据块,将数据提取操作聚焦在每一个数据块上,在每个数据块上可以提取到三个数据,分别是 quote 、author 和通往下一层数据的 链接 ,我们将前两个数据提取出来并通过 链接 发起第二次请求,同时利用 meta
将两个数据传递给第二次请求获得的响应中,最后在第二次相应内容中提取全部的数据。
代码实现
# item.py
class QuotesItem(scrapy.Item):
quote = scrapy.Field()
author = scrapy.Field()
date = scrapy.Field()
# quotes.py
import re
import scrapy
from ..items import QuotesItem
from scrapy import Request
class QuotesSpider(scrapy.Spider):
name = 'Quote'
allowed_domains = ['quotes.toscrape.com']
start_urls = ['http://quotes.toscrape.com/page/1/']
def parse(self, response):
# 第一页一共有 10 个引言,我们将每个引言块视为一个单位,下面的 blocks 就是一个引言块,第一页共有 10 个块。
blocks = response.xpath(
"//div[@class='container']/div[@class='row']/div[@class='col-md-8']/div[@class='quote']").extract()
pattern1 = re.compile(r'<span class="text" itemprop="text">(.*?)</span>')
pattern2 = re.compile(r'<small class="author" itemprop="author">(.*?)</small>')
pattern3 = re.compile(r'<a href="(.*?)">')
# 遍历每一个块,在其中提取 quote 、author 和 链接
for block in blocks:
item = {}
item['quote'] = pattern1.findall(block)[0]
item['author'] = pattern2.findall(block)[0]
link = pattern3.findall(block)[0]
full_link = response.urljoin(link)
# 使用提取到的链接请求 date 数据所在的页面,并且将上面提取到的数据传输到这一次请求的响应内容中,在 Request 中 meta 是字典形式的参数。最后使用 callback 回调指定的函数处理相应内容,在这里指定 parse_date 函数处理相应内容。
yield Request(url=full_link, meta={'k1': item['quote'], 'k2': item['author']}, callback=self.parse_date)
# parse_date 函数处理第二层相应内容 response,现在获得的响应 response 和原始响应不同,因为我们在构造请求时额外添加了一些数据,也就是我们用 meta 传输的数据——一个包含我们需要的数据的字典。
def parse_date(self, response):
item = QuotesItem()
# 我们已经在 response 中添加了 meta 字典,下面是提取字典中的值的方法,和我们平常读取字典中的值的方法是一样的。
item['quote'] = response.meta['k1']
item['author'] = response.meta['k2']
item['date'] = response.xpath("//div[@class='author-details']/p[1]/span[@class='author-born-date']/text()").extract_first()
yield item
# 运行爬虫,并将数据保存在 json 文件中
$ scrapy crawl Quote -o book.json -s FEED_EXPORT_ENCODING=utf-8
我们得到如下结果:第一页的所有目标数据。
[
{"quote": "“A day without sunshine is like, you know, night.”", "author": "Steve Martin", "date": "August 14, 1945"},
{"quote": "“I have not failed. I've just found 10,000 ways that won't work.”", "author": "Thomas A. Edison", "date": "February 11, 1847"},
{"quote": "“It is better to be hated for what you are than to be loved for what you are not.”", "author": "André Gide", "date": "November 22, 1869"},
{"quote": "“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”", "author": "Albert Einstein", "date": "March 14, 1879"},
{"quote": "“It is our choices, Harry, that show what we truly are, far more than our abilities.”", "author": "J.K. Rowling", "date": "July 31, 1965"},
{"quote": "“Imperfection is beauty, madness is genius and it's better to be absolutely ridiculous than absolutely boring.”", "author": "Marilyn Monroe", "date": "June 01, 1926"},
{"quote": "“A woman is like a tea bag; you never know how strong it is until it's in hot water.”", "author": "Eleanor Roosevelt", "date": "October 11, 1884"},
{"quote": "“The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.”", "author": "Jane Austen", "date": "December 16, 1775"}
]
对过程更加清晰的展示
我们在爬取第一组完整数据的过程中一共做了两次请求并且得到两次响应,第一次请求获得了第一层网页,在这个网页下我们提取到了三个数据,这其中包括一个链接,然后我们利用这个链接做了第二次请求,并且在构造请求的时候将前两条数据利用 meta
储存了起来用来传输到第二次请求的响应内容里面。
接下来我们将焦点放在第二次请求获得的响应 response
内容上,我们以上面的代码为基础做一个对比实验,之前我们在 Request
方法中加入了 meta
参数,并且传入了我们想要的数据:
yield Request(url=full_link, meta={'k1': item['quote'], 'k2': item['author']}, callback=self.parse_date)
在这种情况下我们获得的请求是什么呢?我们可以在 IDE
中查看第二次请求获得的响应的详细信息:
查看更多:
- toscrapy.com
- pycharm的调试功能
- Scrapy: How to pass an item between methods using meta
- 一日一技:如何正确在 PyCharm 中调试 Scrapy 爬虫?
本文来自我的博客:Stophana’blog