大师兄的Python学习笔记(三十二): 爬虫(十三)

大师兄的Python学习笔记(三十一): 爬虫(十二)

十一、Scrapy框架

11. 实现通用爬虫
  • 当我们同时爬取多个站点时,可以将各站点爬虫的公用部分保留下来, 将不同的部分提取出来作为作为单独配置。
11.1 关于CrawlSpider
  • CrawlSpider是Scrapy内置的通用爬虫, 可以通过配置规则Rule来定义爬取逻辑。
  • CrawlSpider继承Spider类, 除此之外,还包括一些重要的属性和方法:
属性/方法 介绍
rules Rule对象的列表,CrawlSpider会读取列表中的每一个Rule并解析。
parse_start_url() start_urls里对应的请求得到响应式调用,分析响应并返回Item对象或Request对象,可重写。
link_extractor 是一个Link Extractor对象,爬虫可以从中获得页面提取的链接,并生成新的请求。
callback - 回调函数,从Link_extractor中获取链接时调用。
- 接收response作为第一个参数,并返回一个包含Item或Request对象的列表。
- 应避免使用parse()作为回调函数,会和CrawlSpider的内部方法冲突。
cb_kwargs 传递给回调函数的参数字典。
follow - 决定从response提取的链接是否需要跟进的布尔值。
- 如果callback参数为None,默认为True,否则默认为False。
process_links() 从link_extractor中获取链接列表时调用,主要用于过滤。
process_request() Rule提取到每个Request时调用,返回Request或None,用于处理Request。
  • LinkExtractor还包含一些常用参数:
参数 说明
allow 正则表达式或正则表达式列表 - 一个单一的正则表达式(或正则表达式列表)
- urls必须匹配才能提取。
- 如果没有给出(或为空),它将匹配所有链接。
deny 正则表达式或正则表达式列表 - 一个正则表达式(或正则表达式列表)
- urls必须匹配才能排除(即不提取)。
- 它优先于allow参数。
- 如果没有给出(或为空),它不会排除任何链接。
allow_domains str或list 单个值或包含将被考虑用于提取链接的域的字符串列表
deny_domains str或list 单个值或包含不会被考虑用于提取链接的域的字符串列表
deny_extensions list - 包含在提取链接时应该忽略的扩展的单个值或字符串列表。
- 如果没有给出,它将默认为IGNORED_EXTENSIONS在scrapy.linkextractors包中定义的 列表 。
restrict_xpaths str或list - 是一个XPath(或XPath的列表),它定义响应中应从中提取链接的区域。
- 如果给出,只有那些XPath选择的文本将被扫描链接。
restrict_css str或list - CSS选择器(或选择器列表),用于定义响应中应提取链接的区域。标签(str或list)
- 标签或在提取链接时要考虑的标签列表。默认为。(‘a’, ‘area’)
attrs list - 在查找要提取的链接时应该考虑的属性或属性列表(仅适用于参数中指定的那些标签tags )。
- 默认为(‘href’,)
canonicalize boolean - 规范化每个提取的url(使用w3lib.url.canonicalize_url)。
- 默认为True。
unique boolean 是否应对提取的链接应用重复过滤。
process_value callable - 接收从标签提取的每个值和扫描的属性并且可以修改值并返回新值的函数,或者返回None以完全忽略链接。
- 如果没有给出,process_value默认为。lambda x: x
11.2 关于scrapy.loader.ItemLoader([item,selector,response,] **kwargs)
  • ItemLoader用于对提取Item的规则定义, Item是保存抓取数据的容器,而ItemLoader是填充容器的机制。
  • ItemLoader返回填充过规则的Item对象。
参数 说明
item Item对象,可以调用add_xpath()、add_css()、add_value()等方法填充Item对象。
selector Selector对象,用于提取填充数据的选择器。
response Response对象,用于使用构造选择器的Response。
# 简单案例
>>>from scrapy.loader import ItemLoader
>>>from project.items import Product

>>>def parse(self,response):
>>>      loader = ItemLoader(item=Product(),response=response)
>>>      loader.add_css('name','.product_name')
>>>      return loader.load_item()
  • 此外,ItemLoader每个字段都包含了一个Input Processor和Output Processor:
  1. Input Processor接收数据时立刻提取数据,并将结果保存在ItemLoader内。
  2. load_item()方法调用Output Processor处理数据并填充生成Item对象。
  3. Scrapy内置了一些Processor,可以直接当做Input/Output Processor使用:
内置Processor 说明
Identity 不进行任何处理,直接返回原数据。
TakeFirst 类似extract_first()函数,返回列表的第一个非空值,常用作Output Processor。
Join 类似join()函数,将列表拼成字符串,并用分隔符分开。
Compose 函数组合,依次执行组合中的函数。
MapCompose 与Compose类似,可以迭代处理列表中的值。
SelectJmes 依赖jmespath库,可以通过Key查询JSON的Value。
11.3 使用Crawl Spider
  • 以爬取新闻网站为例。

1) 创建项目

>>>D:\>scrapy startproject universal_scrapy
>>>New Scrapy project 'universal_scrapy', using template directory 'd:\Anaconda3\lib\site->>>>packages\scrapy\templates\project', created in:
>>>    D:\universal_scrapy

>>>You can start your first spider with:
>>>    cd universal_scrapy
>>>    scrapy genspider example example.com

2) 创建Crawl Spider

>>>D:\universal_scrapy>scrapy genspider -t crawl sina sina.com.cn
>>>Created spider 'sina' using template 'crawl' in module:
>>>  universal_scrapy.spiders.sina
  • 生成爬虫文件:
>>># -*- coding: utf-8 -*-
>>>import scrapy
>>>from scrapy.linkextractors import LinkExtractor
>>>from scrapy.spiders import CrawlSpider, Rule


>>>class SinaSpider(CrawlSpider):
>>>    name = 'sina'
>>>    allowed_domains = ['sina.com.cn']
>>>    start_urls = ['http://sina.com.cn/']

>>>    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

3) 定义Rule

  • 修改start_urls为爬取首页:
>>>start_urls = ['http://www.sina.com.cn/']
  • 新建一个LinkExtractor,爬取新闻栏目子页面链接。
>>>rules = (
>>>        Rule(LinkExtractor(allow=r'https:\/\/news.sina.com.cn\/c.*\.shtml',restrict_css='.newslist a',attrs='href'),
>>>             callback='parse_item', follow=True),
>>>    )
  • 由于第一个页面爬取了链接并follow到子页面,所以再增加一格LinkExtractor用于爬取子页面内容。
>>>rules = (
>>>        Rule(LinkExtractor(allow=r'https:\/\/news.sina.com.cn\/c.*\.shtml',restrict_css='.newslist a',attrs='href'),
>>>             callback='parse_item', follow=True),
>>>        Rule(LinkExtractor(restrict_css='.main-content'),follow=False)
>>>    )

4) 解析页面

  • 首先定义Item:
>>>import scrapy

>>>class UniversalScrapyItem(scrapy.Item):
>>>    # define the fields for your item here like:
>>>    title = scrapy.Field()
>>>    url = scrapy.Field()
>>>    time = scrapy.Field()
>>>    content = scrapy.Field()
  • 创建Item Loader。
>>>from scrapy.loader import ItemLoader
>>>from scrapy.loader.processors import *

>>>class ContentLoader(ItemLoader):
>>>    default_onput_processor = TakeFirst()

>>>class SinaLoader(ContentLoader):
>>>    content_out = Compose(Join(),lambda s:s.strip())
  • parse_item()方法用loader定义爬取规则。
>>>from universal_scrapy.items import UniversalScrapyItem

>>>     def parse_item(self, response):
>>>         loader = SinaLoader(item=UniversalScrapyItem(),response=response)
>>>         loader.add_css('title','.main-title::text')
>>>         loader.add_value('url',response.url)
>>>         loader.add_css('time','.date::text')
>>>         loader.add_css('content','.article::text')
>>>         yield loader.load_item()

5) 保存数据

  • 这里直接通过pipeline将内容保存到txt文档。
>>>ITEM_PIPELINES = {
>>>   'universal_scrapy.pipelines.UniversalScrapyPipeline': 300,
>>>}
>>>class UniversalScrapyPipeline(object):
>>>    def process_item(self, item, spider):
>>>        with open("result.txt",'a+') as f:
>>>            f.write(item.get('title')+'\n')
>>>            f.write(item.get('url')+'\n')
>>>            f.write(item.get('time')+'\n')
>>>            f.write(item.get('content')+'\n')
>>>        return item
  • 执行结果:
一觉醒来,枪声响起,美国更大风暴要来了!
https://news.sina.com.cn/c/2020-09-25/doc-iivhvpwy8690800.shtml
2020年09月25日 07:03
原标题:一觉醒来,枪声响起,美国更大风暴要来了!   美国,又一个黑夜要来了!会发>生什么呢?   感觉现在的美国,真陷入了一个恶性循环的泥潭,努力挣扎,但却无力摆脱。   一觉醒来,愤怒的美国人又走上街头,又一次全国性抗议开始了,又有新的悲剧传来。   这一次,大批民众被逮捕,两名警察也倒在了血泊中。更大的撕裂和风暴要来了。 >  综合媒体的报道,最新事件,脉络大致如下。   1,焦点城市是肯塔基最大城市路易斯维尔市,9月23日,大批民众走上街头,强烈抗议不公正的判决。随后爆发了激烈冲突,混乱局势中,有人朝警察开枪射击,两名警察受伤,被送往医院急救。   2,在枪击前几个小时,肯塔基检察长卡梅伦宣布,26岁黑人女子布伦娜·泰勒被杀案,大陪审团已决定,对两名开枪警察不予起诉,另一名警察子弹射入泰勒邻居家,因此,这名警察被指控3项肆意危害罪
... ...
11.4 通用爬虫改造
  • 为了爬虫能够被多个爬虫共用,需要将不同的部分抽取出来作为配置文件。

1) 将爬虫属性抽离

  • 抽离成sina.json放在configs文件夹中。
  • 抽离爬虫配置:
{
 "spider": "sina",
 "website": "www.sina.com.cn",
 "type": "news",
 "settings": {
     "User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"
 },
 "start_urls": [
   "http://www.sina.com.cn/"
 ],
 "allowed_domains":[
   "sina.com.cn"
 ],
 "rules": "sina"
}
  • 抽离loader配置:
"item": {
   "class": "UniversalScrapyItem",
   "loader": "SinaLoader",
   "attrs": {
     "title": [
         {
         "method": "css",
         "args": [
           ".main-title::text"
         ]
       }
     ],
     "url": [
         {
         "method": "attr",
         "args": [
           "url"
         ]
       }
     ],
     "time": [
         {
         "method": "css",
         "args": [
           ".date::text"
         ]
       }
     ],
     "content": [
         {
         "method": "css",
         "args": [
           ".article p::text"
         ]
       }
     ]
   }
 }

2) 将Rules抽离

  • 抽离成单独的rules.py文件。
>>>from scrapy.linkextractors import LinkExtractor
>>>from scrapy.spiders import Rule

>>>rules = {
>>>    "sina":(
           >>>>Rule(LinkExtractor(allow=r'https:\/\/news.sina.com.cn\/c.*\.shtml',restrict_css='.newslist a',attrs='href'),
>>>                 callback='parse_item', follow=True),
>>>            Rule(LinkExtractor(restrict_css='.main-content'),follow=False)
>>>        )
>>>}

3) 创建配置文件的读取方法

  • 放在工具文件utils.py中,用于从config.py中读取配置信息。
>>>from os.path import realpath,dirname,join
>>>import json
>>>def get_config(name):
>>>    path = join(realpath(__file__)) + join('/configs/',f'{name}.json'
>>>    with open(path,'r',encoding='utf-8') as f:
>>>        return json.loads(f.read())

4) 创建入口文件

  • 创建run.py用于启动爬虫。
>>>import sys
>>>from scrapy.utils.project import get_project_settings
>>>from universal_scrapy.spiders.sina import SinaSpider
>>>from universal_scrapy.utils import get_config
>>>from scrapy.crawler import CrawlerProcess

>>>def run():
>>>    name = sys.argv[1] # 获得输入名称
>>>    custom_settings = get_config(name) # 获得指定配置参数
>>>    spider = custom_settings.get('spider','sina') # 获得爬虫名
>>>    project_settings = get_project_settings() # 获得全局参数
>>>    settings = dict(project_settings.copy()) 
>>>    settings.update(custom_settings.get('settings')) # 参数合并
>>>    process = CrawlerProcess(settings) # 启动爬虫
>>>    process.crawl(spider,**{'name':name})
>>>    process.start()

>>>if __name__ == '__main__':
>>>    run()

5) 修改爬虫

  • 将爬虫中的固定参数改成对接初始化配置。
>>>from scrapy.spiders import CrawlSpider
>>>from scrapy.loader import ItemLoader
>>>from scrapy.loader.processors import TakeFirst,Compose,Join
>>>from universal_scrapy.utils import get_config
>>>from universal_scrapy.rules import rules

>>>class ContentLoader(ItemLoader):
>>>    default_output_processor = TakeFirst()

>>>class SinaLoader(ContentLoader):
>>>    content_out = Compose(Join(),lambda s:s.strip())

>>>class SinaSpider(CrawlSpider):
>>>    name = 'sina'

>>>    def __init__(self,name, *args, **kwargs):
>>>        config = get_config(name)
>>>        self.config = config
>>>        self.rules = rules.get(config.get('rules'))
>>>        self.start_urls = config.get('start_urls')
>>>        self.allowed_domains = config.get('allowed_domaines')
>>>        super(SinaSpider,self).__init__(*args,**kwargs)

>>>    def parse_item(self, response):
>>>        item = self.config.get('item')
>>>        if item:
>>>            cls = eval(item.get('class'))()
>>>            loader = eval(item.get('loader'))(cls,response=response)
>>>            for k,v in item.get('attrs').items():
>>>                for extractor in v:
>>>                    print(extractor)
>>>                    if extractor.get('method') =='css':
>>>                        loader.add_css(k,*extractor.get('args'),**{'re':extractor.get('re')})
>>>                    elif extractor.get('method') == 'attr':
>>>                        loader.add_value(k,getattr(response,*extractor.get('args')))

>>>            yield loader.load_item()
  • 运行爬虫:
D:\universal_scrapy>python run.py sina
  • 获得结果:
一觉醒来,枪声响起,美国更大风暴要来了!
https://news.sina.com.cn/c/2020-09-25/doc-iivhvpwy8690800.shtml
2020年09月25日 07:03
原标题:一觉醒来,枪声响起,美国更大风暴要来了!   美国,又一个黑夜要来了!会发生什么呢?   感觉现在的美国,真陷入了一个恶性循环的泥潭,努力挣扎,但却无力摆脱。   一觉醒来,愤怒的美国人又走上街头,又一次全国性抗议开始了,又有新的悲剧传来。   这一次,大批民众被逮捕,两名警察也倒在了血泊中。更大的撕裂和风暴要来了。   综合媒体的报道,最新事件,脉络大致如下。   1,焦点城市是肯塔基最大城市路易斯维尔市,9月23日,大批民众走上街头,强烈抗议不公正的判决。随后爆发了激烈冲突,混乱局势中,有人朝警察开枪射击,两名警察受伤,被送往医院急救。   2,在枪击前几个小时,肯塔基检察长卡梅伦宣布,26岁黑人女子布伦娜·泰勒被杀案,大陪审团已决定,对两名
... ...

参考资料


  • https://blog.csdn.net/u010138758/article/details/80152151 J-Ombudsman
  • https://www.cnblogs.com/zhuluqing/p/8832205.html moisiet
  • https://www.runoob.com 菜鸟教程
  • http://www.tulingxueyuan.com/ 北京图灵学院
  • http://www.imooc.com/article/19184?block_id=tuijian_wz#child_5_1 两点水
  • https://blog.csdn.net/weixin_44213550/article/details/91346411 python老菜鸟
  • https://realpython.com/python-string-formatting/ Dan Bader
  • https://www.liaoxuefeng.com/ 廖雪峰
  • https://blog.csdn.net/Gnewocean/article/details/85319590 新海说
  • https://www.cnblogs.com/Nicholas0707/p/9021672.html Nicholas
  • https://www.cnblogs.com/dalaoban/p/9331113.html 超天大圣
  • https://blog.csdn.net/zhubao124/article/details/81662775 zhubao124
  • https://blog.csdn.net/z59d8m6e40/article/details/72871485 z59d8m6e40
  • https://www.jianshu.com/p/2b04f5eb5785 MR_ChanHwang
  • 《Python学习手册》Mark Lutz
  • 《Python编程 从入门到实践》Eric Matthes
  • 《Python3网络爬虫开发实战》崔庆才

本文作者:大师兄(superkmi)

你可能感兴趣的:(大师兄的Python学习笔记(三十二): 爬虫(十三))