【Python爬虫】专栏简介:本专栏是 Python 爬虫领域的集大成之作,共 100 章节。从 Python 基础语法、爬虫入门知识讲起,深入探讨反爬虫、多线程、分布式等进阶技术。以大量实例为支撑,覆盖网页、图片、音频等各类数据爬取,还涉及数据处理与分析。无论是新手小白还是进阶开发者,都能从中汲取知识,助力掌握爬虫核心技能,开拓技术视野。
目录
- 一、引言
- 二、编写不同类型的 Spider
- 2.1 CrawlSpider
- 2.2 XMLFeedSpider
- 三、Spider 的规则定义与链接提取
- 3.1 LinkExtractor 概述
- 3.2 规则定义示例
- 3.3 实际应用中的链接提取技巧
- 四、在 Spider 中处理复杂的网页逻辑
- 4.1 分页处理
- 4.2 表单提交
- 五、总结与展望
一、引言
在 “Python 爬虫” 专栏的学习旅程中,我们已经深入了解了 Python 基础语法以及爬虫的入门知识,并且对 Scrapy 框架也有了一定程度的认识。而在 Scrapy 框架里,Spider 扮演着核心角色,它就像是一个智能探险家,根据我们设定的规则,在网页的海洋中精准地获取所需数据。本章节我们将深入探讨 Scrapy 的 Spider 开发,从编写不同类型的 Spider,到规则定义与链接提取,再到复杂网页逻辑的处理,全面掌握 Spider 开发的精髓,为我们的爬虫开发技能添砖加瓦。
二、编写不同类型的 Spider
2.1 CrawlSpider
CrawlSpider 是 Scrapy 中用于编写通用爬虫的类,它能够自动根据我们定义的规则跟踪链接,从而实现对网站的深度爬取。在实际应用中,当我们面对一个具有复杂链接结构和大量页面的网站时,CrawlSpider 就显得尤为重要。例如,对于一个新闻资讯网站,它的文章分布在不同的栏目和分页中,使用 CrawlSpider 可以方便地定义规则,自动遍历所有页面并获取新闻内容。
CrawlSpider 的规则定义主要通过rules属性来实现,rules是一个包含多个Rule对象的元组。每个Rule对象定义了如何从页面中提取链接以及如何处理这些链接。Rule对象的主要参数包括:
- link_extractor:一个LinkExtractor对象,用于定义从页面中提取链接的规则。
- callback:一个回调函数,用于处理提取到的链接对应的页面。
- follow:一个布尔值,指定是否跟进从当前页面提取到的链接。
下面通过一个代码示例来展示如何创建一个 CrawlSpider:
import scrapy
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
class ExampleCrawlSpider(CrawlSpider):
name = 'example_crawl'
allowed_domains = ['example.com']
start_urls = ['http://www.example.com']
rules = (
Rule(LinkExtractor(allow=r'category/'), callback='parse_category', follow=True),
Rule(LinkExtractor(allow=r'article/'), callback='parse_article', follow=False),
)
def parse_category(self, response):
# 处理分类页面,提取分类信息
category_name = response.css('h1.category-title::text').get()
self.logger.info('Category: %s', category_name)
def parse_article(self, response):
# 处理文章页面,提取文章信息
article_title = response.css('h1.article-title::text').get()
article_content = response.css('div.article-content::text').get()
yield {
'title': article_title,
'content': article_content
}
在上述代码中:
- allowed_domains指定了允许爬取的域名,防止爬虫爬取到其他不相关的网站。
- start_urls设置了爬虫的起始 URL,爬虫将从这些 URL 开始发起请求。
- rules定义了两个规则:
-
- 第一个规则使用LinkExtractor提取所有匹配category/的链接,将这些链接对应的页面交给parse_category方法处理,并跟进这些链接继续爬取。
-
- 第二个规则提取所有匹配article/的链接,交给parse_article方法处理,但不跟进这些链接。
2.2 XMLFeedSpider
XMLFeedSpider 主要用于解析 XML 格式的数据,它适用于处理一些以 XML 格式提供数据的网站,比如 RSS 订阅源。在当今信息爆炸的时代,许多网站提供 RSS 订阅服务,方便用户获取最新的内容更新。使用 XMLFeedSpider 可以轻松地从这些 RSS 订阅源中提取出文章标题、链接、发布时间等信息。
XMLFeedSpider 的工作原理是通过迭代 XML 文档中的节点来提取数据。它提供了一些重要的属性和方法:
- iterator:指定使用的迭代器,默认为iternodes,这是一个基于正则表达式的高性能迭代器。还可以设置为html或xml迭代器。
- itertag:设置开始迭代的节点,即从哪个节点开始提取数据。
- parse_node:在节点与所提供的标签名相符合时被调用,在这个方法中定义信息提取和处理的操作。
下面是一个使用 XMLFeedSpider 的代码示例:
import scrapy
from scrapy.spiders import XMLFeedSpider
class ExampleXMLFeedSpider(XMLFeedSpider):
name = 'example_xml'
allowed_domains = ['example.com']
start_urls = ['http://www.example.com/rss.xml']
iterator = 'iternodes'
itertag = 'item'
def parse_node(self, response, selector):
item = {}
item['title'] = selector.xpath('title/text()').get()
item['link'] = selector.xpath('link/text()').get()
item['pub_date'] = selector.xpath('pubDate/text()').get()
yield item
在这个示例中:
- start_urls指定了要爬取的 XML 文件的 URL,这里是一个 RSS 订阅源。
- iterator设置为iternodes,使用默认的迭代器。
- itertag设置为item,表示从节点开始迭代提取数据。
- 在parse_node方法中,使用 XPath 表达式从 XML 节点中提取文章的标题、链接和发布时间,并将这些数据封装成一个字典返回。
三、Spider 的规则定义与链接提取
3.1 LinkExtractor 概述
在 Scrapy 的 Spider 开发中,链接提取是一个关键环节。LinkExtractor 作为 Scrapy 中专门用于提取链接的工具,为我们提供了强大且灵活的链接提取功能。它能够从网页的 HTML 或 XML 内容中按照我们设定的规则精准地提取出链接,为爬虫的后续爬取工作提供目标。
LinkExtractor 有许多常用参数,这些参数赋予了它强大的链接筛选能力:
- allow:接收一个正则表达式或一个正则表达式列表,用于提取绝对 url 与正则表达式匹配的链接。例如,当我们爬取一个电商网站时,如果只想提取商品详情页的链接,就可以设置allow=r’product/\d+.html’,这样就只会提取符合该正则表达式的链接,如product/123.html。如果该参数为空(默认),则会提取全部链接。
- deny:与allow相反,接收一个正则表达式或一个正则表达式列表,用于排除绝对 url 与正则表达式匹配的链接。比如在爬取一个包含多种页面类型的网站时,我们不想爬取关于网站介绍、联系我们等页面的链接,就可以设置deny=r’(about|contact).html’,这样就会排除掉这些匹配的链接。
- restrict_xpaths:接收一个 XPath 表达式或一个 XPath 表达式列表,用于提取 XPath 表达式选中区域下的链接。假设我们要从一个新闻网站的首页提取新闻列表区域的链接,而新闻列表在一个特定的div标签下,我们就可以使用restrict_xpaths=‘//div[@class=“news-list”]’,这样就只会从这个新闻列表区域提取链接,避免提取到其他无关区域的链接。
此外,还有allow_domains(提取到指定域的链接)、deny_domains(排除到指定域的链接)、restrict_css(提取 CSS 选择器选中区域下的链接)、tags(提取指定标签内的链接,默认为[‘a’, ‘area’])、attrs(提取指定属性内的链接,默认为[‘href’])、process_value(对提取的每一个链接进行处理的回调函数)等参数,这些参数可以根据具体需求灵活组合使用,实现各种复杂的链接提取需求。
3.2 规则定义示例
在 CrawlSpider 中,我们通常会结合 LinkExtractor 来定义规则。下面通过一个具体的示例来展示如何在 Rule 中使用 LinkExtractor:
假设我们要爬取一个小说网站,网站的小说列表页面链接格式为http://www.example.com/novel/list/?page=1,http://www.example.com/novel/list/?page=2等,小说详情页链接格式为http://www.example.com/novel/detail/123,其中123是小说的 ID。我们可以定义如下规则:
import scrapy
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
class NovelSpider(CrawlSpider):
name = 'novel'
allowed_domains = ['example.com']
start_urls = ['http://www.example.com/novel/list/?page=1']
rules = (
Rule(LinkExtractor(allow=r'list/\?page=\d+'), follow=True),
Rule(LinkExtractor(allow=r'detail/\d+'), callback='parse_novel', follow=False),
)
def parse_novel(self, response):
novel_title = response.css('h1.novel-title::text').get()
novel_content = response.css('div.novel-content::text').get()
yield {
'title': novel_title,
'content': novel_content
}
在这个示例中:
- 第一个规则使用LinkExtractor提取所有匹配list/?page=\d+的链接,即小说列表页的链接。由于设置了follow=True,爬虫会跟进这些链接,继续爬取后续的列表页。
- 第二个规则提取所有匹配detail/\d+的链接,即小说详情页的链接,并将这些链接对应的页面交给parse_novel方法处理。由于设置了follow=False,爬虫不会再从小说详情页中提取链接并跟进。
3.3 实际应用中的链接提取技巧
在实际的网页爬取中,经常会遇到复杂的网页结构,这就需要一些特殊的技巧来精准提取链接:
- 处理多层嵌套标签:有些网页的链接可能嵌套在多层标签中,例如:
<div class="outer">
<div class="middle">
<a href="http://example.com/link1">Link 1</a>
</div>
</div>
如果我们使用restrict_xpaths参数,可以这样设置:restrict_xpaths=‘//div[@class=“outer”]/div[@class=“middle”]’,这样就能准确地从这个多层嵌套的结构中提取出链接。如果使用 CSS 选择器,可以设置为restrict_css=‘.outer.middle’。
- 动态生成链接:对于一些通过 JavaScript 动态生成的链接,常规的 LinkExtractor 可能无法直接提取。这时可以考虑使用 Selenium 等工具来模拟浏览器行为,等待页面加载完成并生成链接后再进行提取。例如,使用 Selenium 打开网页,等待一段时间让 JavaScript 执行完毕,然后获取页面的源代码,再将其传递给 Scrapy 的 LinkExtractor 进行链接提取。具体实现可以参考以下代码:
from selenium import webdriver
from scrapy.http import HtmlResponse
# 使用Selenium获取网页内容
driver = webdriver.Chrome()
driver.get('http://example.com')
# 等待页面加载,假设等待5秒
import time
time.sleep(5)
html = driver.page_source
driver.quit()
# 将获取的内容转换为Scrapy的Response对象
response = HtmlResponse(url='http://example.com', body=html, encoding='utf-8')
# 使用LinkExtractor提取链接
from scrapy.linkextractors import LinkExtractor
link_extractor = LinkExtractor()
links = link_extractor.extract_links(response)
for link in links:
print(link.url)
通过这些技巧,我们能够在复杂的网页环境中更准确地提取所需链接,提高爬虫的效率和准确性。
四、在 Spider 中处理复杂的网页逻辑
4.1 分页处理
在爬虫开发中,分页是一个非常常见的需求。许多网站为了提高用户体验和数据加载效率,会将数据分成多个页面展示。以电商网站为例,商品列表通常会分页显示,每页展示一定数量的商品;新闻网站的文章列表也会采用分页的形式,方便用户浏览不同时期的新闻。因此,实现分页处理对于爬虫获取完整的数据至关重要。
在 Scrapy 中,实现分页的方式有多种,其中一种常见的方法是通过分析 URL 规律来构造下一页的请求。例如,对于一个分页的新闻列表页面,其 URL 可能具有如下规律:http://www.example.com/news/list?page=1,http://www.example.com/news/list?page=2,http://www.example.com/news/list?page=3等。我们可以通过观察发现,URL 中page参数的值随着页码的增加而递增。在 Scrapy 中,我们可以利用这一规律来实现分页:
import scrapy
class NewsSpider(scrapy.Spider):
name = 'news'
start_urls = ['http://www.example.com/news/list?page=1']
def parse(self, response):
# 提取当前页面的新闻数据
for news in response.css('div.news-item'):
item = {
'title': news.css('h2.title::text').get(),
'content': news.css('p.content::text').get()
}
yield item
# 提取下一页的链接
next_page = response.css('a.next::attr(href)').get()
if next_page:
# 拼接完整的下一页URL
next_page_url = response.urljoin(next_page)
# 发送下一页的请求,并指定回调函数为parse
yield scrapy.Request(next_page_url, callback=self.parse)
在上述代码中:
- 在parse方法中,首先提取当前页面的新闻数据,并将其封装成item字典返回。
- 然后通过response.css(‘a.next::attr(href)’).get()提取下一页的链接。
- 使用response.urljoin(next_page)拼接完整的下一页 URL,确保链接的正确性。
- 最后使用yield scrapy.Request(next_page_url, callback=self.parse)发送下一页的请求,并指定回调函数为parse,这样当获取到下一页的响应时,会继续调用parse方法进行数据提取和下一页链接的提取,从而实现分页爬取。
另一种常用的方法是使用response.follow方法来跟进链接实现分页。response.follow方法是 Scrapy 提供的一个便捷方法,它可以自动处理相对链接和绝对链接的拼接,并且会自动设置请求的相关参数。例如:
import scrapy
class NewsSpider(scrapy.Spider):
name = 'news'
start_urls = ['http://www.example.com/news/list?page=1']
def parse(self, response):
# 提取当前页面的新闻数据
for news in response.css('div.news-item'):
item = {
'title': news.css('h2.title::text').get(),
'content': news.css('p.content::text').get()
}
yield item
# 提取下一页的链接并跟进
next_page = response.css('a.next::attr(href)').get()
if next_page:
yield response.follow(next_page, self.parse)
在这个示例中,yield response.follow(next_page, self.parse)实现了跟进下一页链接的操作,它与yield scrapy.Request(next_page_url, callback=self.parse)的效果类似,但response.follow方法更加简洁和方便 。
4.2 表单提交
表单提交在爬虫中有着广泛的应用场景,比如模拟用户登录、执行搜索操作等。在登录场景中,我们需要向服务器提交用户名和密码等信息,以获取登录后的权限,从而访问一些需要登录才能查看的页面;在搜索场景中,我们通过提交关键词等参数,获取相关的搜索结果页面。
在 Scrapy 中,使用FormRequest类来发送表单请求。FormRequest是Request的子类,专门用于处理表单提交。下面通过一个模拟登录的示例来展示如何使用FormRequest:
import scrapy
class LoginSpider(scrapy.Spider):
name = 'login'
start_urls = ['http://www.example.com/login']
def parse(self, response):
# 构造表单数据
form_data = {
'username': 'your_username',
'password': 'your_password'
}
# 发送表单请求
yield scrapy.FormRequest.from_response(
response,
formdata=form_data,
callback=self.after_login
)
def after_login(self, response):
# 登录成功后处理响应
if 'Login successful' in response.text:
self.logger.info('Login successful')
# 继续进行后续的爬取操作
yield scrapy.Request('http://www.example.com/protected_page', callback=self.parse_protected)
else:
self.logger.error('Login failed')
def parse_protected(self, response):
# 处理需要登录后才能访问的页面
data = response.css('div.protected-content::text').get()
yield {
'protected_data': data
}
在上述代码中:
- 在parse方法中,首先构造了包含用户名和密码的表单数据form_data。
- 使用scrapy.FormRequest.from_response方法从当前响应中生成一个表单请求。from_response方法会自动从响应中提取表单信息,并根据我们提供的formdata参数填充表单数据。
- 指定callback为after_login,表示在表单提交成功后,会调用after_login方法来处理响应。
- 在after_login方法中,根据响应内容判断登录是否成功。如果登录成功,继续发送请求访问需要登录后才能访问的页面,并指定回调函数为parse_protected;如果登录失败,则记录错误信息。
- 在parse_protected方法中,处理需要登录后才能访问的页面,提取其中的关键数据。
当处理一些需要动态生成表单参数的情况时,我们可能需要先解析页面,获取一些隐藏的参数,然后再构造表单数据。例如,有些网站在登录表单中会包含一个 CSRF(跨站请求伪造)令牌,这个令牌是动态生成的,每次页面加载时都可能不同。在这种情况下,我们需要先从页面中提取这个令牌,然后将其包含在表单数据中进行提交:
import scrapy
class LoginSpider(scrapy.Spider):
name = 'login'
start_urls = ['http://www.example.com/login']
def parse(self, response):
# 提取CSRF令牌
csrf_token = response.css('input[name="csrf_token"]::attr(value)').get()
# 构造表单数据
form_data = {
'username': 'your_username',
'password': 'your_password',
'csrf_token': csrf_token
}
# 发送表单请求
yield scrapy.FormRequest.from_response(
response,
formdata=form_data,
callback=self.after_login
)
def after_login(self, response):
# 登录成功后处理响应
if 'Login successful' in response.text:
self.logger.info('Login successful')
# 继续进行后续的爬取操作
yield scrapy.Request('http://www.example.com/protected_page', callback=self.parse_protected)
else:
self.logger.error('Login failed')
def parse_protected(self, response):
# 处理需要登录后才能访问的页面
data = response.css('div.protected-content::text').get()
yield {
'protected_data': data
}
在这个示例中,通过response.css(‘input[name=“csrf_token”]::attr(value)’).get()从页面中提取了 CSRF 令牌,并将其添加到表单数据中,确保表单提交的正确性和安全性。通过这些方法,我们能够灵活地处理各种表单提交的场景,满足爬虫在不同网页环境下的需求。
五、总结与展望
在本章节中,我们深入探索了 Scrapy 的 Spider 开发。从编写不同类型的 Spider,如 CrawlSpider 和 XMLFeedSpider,了解它们各自的适用场景和实现方式;到学习 Spider 的规则定义与链接提取,掌握 LinkExtractor 的强大功能和使用技巧,能够在复杂的网页结构中精准地提取所需链接;再到处理复杂的网页逻辑,如分页处理和表单提交,让爬虫能够适应各种实际的网页爬取需求。这些知识和技能是我们在 Scrapy 爬虫开发道路上的重要基石。
理论知识固然重要,但实践才是检验真理的唯一标准。希望读者能够积极动手实践,将本章节所学应用到实际的项目中。通过不断地练习,加深对 Scrapy Spider 开发的理解和掌握,提高自己的爬虫开发能力。
下一章节,我们将继续深入 Scrapy 的世界,探讨反爬虫技术与应对策略。在如今的网络环境下,反爬虫技术日益成熟,爬虫开发者需要不断学习和探索新的方法来突破反爬虫机制,获取所需的数据。让我们一起期待下一章节的精彩内容,继续提升我们的爬虫技能。