CrawlSpider类的功能比Spider类要强大很多,大多数爬虫程序都采用CrawlSpider类。在百度新闻爬虫项目中,增加一个CrawlSpider爬虫,用于专门爬取百家号的文章。
1、 如何爬取百家号的文章?
CrawlSpider爬虫将从百度新闻首页爬取,过滤掉所有非百家号文章的链接,下载百家号文章网页,从百家号文章网页提取文章内容存储到CSV文件。
具体开发过程遵循下面的步骤:
(1)使用Scrapy命令创建CrawlSpider爬虫;
(2)定义一个存储百家号文章的Item数据容器;
(3)定义数据处理ItemLoader类,用于去掉文章内容的HTML标签;
(4)定义Pipeline管道类,将爬取的数据存储到CSV文件;
(5)编辑CrawlSpider爬虫代码。
2、 创建CrawlSpider爬虫
创建CrawlSpider爬虫,使用下面的命令:
scrapy genspider -t crawl spider_bjhbaidu https://news.baidu.com
创建爬虫命令使用了命令选项-t,用于设置爬虫的模板,爬虫模板为crawl,spider_bjhbaidu是爬虫名称,https://news.baidu.com是该爬虫要爬取的网站域名。
设置项目所在目录为当前工作目录,在Windows命令行窗口输入下面的命令:
scrapy genspider -t crawl spider_bjhbaidu https://news.baidu.com
命令执行完成后,在项目的spiders目录下创建了模块文件spider_bjhbaidu.py,该模块文件就是CrawlSpider爬虫代码。
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
class SpiderBjhbaiduSpider(CrawlSpider):
name = 'spider_bjhbaidu'
allowed_domains = ['https://news.baidu.com']
start_urls = ['http://https://news.baidu.com/']
rules = (
Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
)
def parse_item(self, response):
item = {}
#item['domain_id'] = response.xpath('//input[@id="sid"]/@value').get()
#item['name'] = response.xpath('//div[@id="name"]').get()
#item['description'] = response.xpath('//div[@id="description"]').get()
return item从代码中可以看出,SpiderBjhbaiduSpider继承了CrawlSpider类,Request请求响应默认处理的方法是parse_item(),该方法替换了parse()方法。CrawlSpider中不能再有以parse为名字的请求响应处理方法,这个方法被CrawlSpider用来实现基础url提取等功能。
代码中的rules定义了一个URL提取规则,Rule()方法返回一个规则。Rule()方法的第一个参数是LinkExtractor实例对象,它使用正则表达式和XPath表达式来过滤从网页中提取的URL链接;Rule()方法的第二个参数callback用来设置请求响应的回调方法,默认回调方法是parse_item;Rule()方法的第三个参数follow用于设置根据该规则从response提取的链接是否继续爬取。
在编写爬虫代码之前,需要先完成Item数据容器、数据处理类ItemLoader类、Pipeline管道类的定义。
3、 定义Item数据容器
百家号文章存储4个数据项(也称为字段),分别是作者、文章标题、文章内容、发布时间。
在百度新闻项目包目录下,新建article_item.py模块文件。代码如下:
import scrapy # 定义结构化数据Article类 # Article类继承scrapy.Item类 class Article(scrapy.Item): # 定义title数据项,存储文章标题 title = scrapy.Field() # 定义author数据项,存储文章作者 author = scrapy.Field() # 定义content数据项,存储文章内容 content = scrapy.Field() # 定义date数据项,存储文章发布时间 date = scrapy.Field()
4、定义数据处理ItemLoader类
百家号文章数据爬取完成后,需要对数据进行处理(也称为清洗),过滤掉非必要的数据,提取需要的数据,清洗前的数据也称为脏数据。
在清洗任何脏数据之前,首先要理解脏数据的构成,才能确定要做哪些数据的清洗工作。ItemLoader类主要对百家号文章的4项数据进行清洗,分别是文章标题、文章作者、文章内容和文章发布时间。
文章标题数据的清洗
观察百家号文章网页的源代码,文章标题数据项构成如下:
<div> <h2>张大奕:新品两秒被抢空,28分钟卖货1个亿,却这么快就要转型</h2> </div>
在爬虫类的解析方法中可以使用XPath表达式直接提取干净且完整的标题内容,该数据项无需进行清洗。
文章作者数据的清洗
观察百家号文章网页的源代码,文章作者数据项构成如下:
<p>瓶子说交易</p>
在爬虫类的解析方法中可以使用XPath表达式直接提取干净且完整的文章作者内容,该数据项无需进行清洗。
文章内容数据的清洗
观察百家号文章网页的源代码,文章内容为HTML数据,需要对文章内容进行清洗,过滤掉HTML标签。
文章发布时间数据的清洗
观察百家号文章网页的源代码,文章发布时间数据项构成如下:
<span>发布时间:10-06</span>
爬虫提取的发布时间不是标准时间格式,需要对提取的发布时间进行清洗,将时间转换为标准时间格式:XXXX-XX-XX(例如:2020-10-06)。
在百度新闻项目包目录下,新建article_itemload.py模块文件。代码如下:
# 导入时间处理类datetime
from datetime import datetime
# 导入默认的处理器
from scrapy.loader.processors import Compose
# 导入ItemLoader类
from scrapy.loader import ItemLoader
# 导入正则模块
import re
# 列表转换为字符串对象
def tosting(value):
if type(value) is list:
data = ""
# 合并列表元素
for item in value:
data += item
return data
# 去掉所有HTML标签,仅保留文本
def remove_tag(value):
pattern = re.compile(r'<[^>]+>',re.S)
result = pattern.sub('',value)
return result
# 转换为标准时间格式XXXX-XX-XX
def convert_date(date):
# 获取当前年份
value = str(datetime.now().year) + "-"
for ch in date:
if ch.isdigit() or ch == '-':
value+=ch
return value
# 定义BaiJiaHaoLoader类,用于对数据的清洗
# BaiJiaHaoLoader类继承ItemLoader类
class BaiJiaHaoLoader(ItemLoader):
# 定义数据项content输入处理器
content_in = Compose(tosting,remove_tag)
# 定义数据项date的输入处理器
date_in = Compose(tosting,convert_date)tosting(value)函数将列表所有的元素合并为一个字符串对象。
remove_tag(value)函数使用正则表达式过滤掉value所有的HTML标签。
convert_date(date)函数将提取的时间转换为标准时间格式XXXX-XX-XX。
5、 定义Pipeline管道类
爬取的数据要存储到CSV文件,因此要定义一个负责存储爬取数据的Pipeline管道类。
在项目包目录下,新建pipelinesbjhcsv.py模块文件,代码如下:
# 导入csv模块 import csv import os class BjhPipeline: # 定义构造方法,打开CSV文件 def __init__(self): # 定义CSV文件的存储路径 store_file = os.path.dirname(__file__) + '/bjh.csv' # 以a+模式创建CSV文件 self.file = open(store_file,'a+',newline='') # 实例化writer对象 self.writer = csv.writer(self.file) def process_item(self, item, spider): # 获得item数据项适配器 adapter = ItemAdapter(item) if adapter: # row为列表对象 row = [adapter['title'][0], adapter['author'][0], adapter['content'][0], adapter['date'][0]] # row写入CSV文件 self.writer.writerow(row) return item # 爬虫程序关闭时,该方法被调用 def close_spider(self,spider): # 关闭文件 self.file.close()
6、 编辑CrawlSpider爬虫代码
最后剩下的工作就是编辑CrawlSpider爬虫代码,CrawlSpider爬虫在前面已经创建成功了,爬虫模块文件名称是spider_bjhbaidu.py。
完整的spider_bjhbaidu.py代码文件如下:
# 导入Item数据类Article
from newsbaidu.article_item import Article
# 导入scrapy选择器
from scrapy.selector import Selector
# 导入BaiduNewsLoader
from newsbaidu.article_itemload import BaiJiaHaoLoader
# 导入scrapy
import scrapy
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
class SpiderBjhbaiduSpider(CrawlSpider):
name = 'spider_bjhbaidu'
allowed_domains = ['https://news.baidu.com/','baijiahao.baidu.com']
start_urls = ['https://news.baidu.com/']
# 定义爬虫内置配置项,覆盖配置文件的配置项
# 配置爬虫使用的管道类
custom_settings = {
'ITEM_PIPELINES':{
'newsbaidu.pipelinesbjhcsv.BjhPipeline': 302,
}
}
# 定义爬取规则,仅爬取包含baijiahao.baidu.com的链接
rules = (
Rule(LinkExtractor(allow=r'.*baijiahao.baidu.com.*'), callback='parse_item', follow=True),
)
def parse_item(self, response):
# 获取爬取下来的网页代码
html = response.text
# 实例化BaiJiaHaoLoader对象
loader = BaiJiaHaoLoader(item=Article(), selector=Selector(response=response))
# 获取标题数据项
loader.add_xpath("title","//div[@class='article-title']/h2/text()")
# 获取文章作者数据项
loader.add_xpath("author","//div[@class='author-txt']/p/text()")
# 获取文章发布时间数据项
loader.add_xpath("date","//span[@class='date']/text()")
#获取文章内容
content = response.xpath("//div[@class='article-content']/*").extract()
loader.add_value("content",content)
value = loader.get_collected_values("title")
# 若value不为None,调用yield语句返回Item数据项
# 过滤掉无内容的链接
if value:
yield loader.load_item()BjhPipeline管道类并没有放置在项目的配置文件内,而是在爬虫内使用属性custom_settings进行配置,避免了Scrapy调用配置文件配置的管道类。