大师兄的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岁黑人女子布伦娜·泰勒被杀案,大陪审团已决定,对两名
... ...

参考资料



本文作者:大师兄(superkmi)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350