欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

scrapy爬虫之item/itemloader机制爬取豆瓣电影top250

程序员文章站 2022-04-26 15:44:43
...

简介

前面的博文网页的基本解析流程就是先通过 css/xpath 方法进行解析,然后再把值封装到 Item 中,如果有特殊需要的话还要对解析到的数据进行转换处理,这样当解析代码或者数据转换要求过多的时候,会导致代码量变得极为庞大,从而降低了可维护性。同时在 sipider 中编写过多的数据处理代码某种程度上也违背了单一职责的代码设计原则。我们需要使用一种更加简洁的方式来获取与处理网页数据,ItemLoader 就是用来完成这件事情的。
因此,我们从一开始就应该适应item/itemloader来解析数据。

网页分析

scrapy爬虫之item/itemloader机制爬取豆瓣电影top250

黄色标注的就是我们需要抓取的内容。

实现

一、定义item类
为了将网页解析后获取的数据进行格式化,便于数据的传递与进一步的操作,Scrapy 提供了 Item 类来对数据进行封装。
要使用 Item 类非常简单,直接继承 scrapy 的 Item 类即可,然后可以定义相应的属性字段来对数据进行存储,其字段类型为 scrapy.Field()。 Scrapy 只提供了 Field() 一种字段类型,可以用来存储任意类型的数据

vim item.py
from scrapy.loader.processors import Join, MapCompose, TakeFirst
class Movietop250(scrapy.Item):
    rank = scrapy.Field()
    title = scrapy.Field(output_processor=Join())
    link = scrapy.Field()
    star = scrapy.Field(output_processor=Join(','))
    quote = scrapy.Field()

从上图看出:
title有title和other标签,此处我们需要使用itemloader的output_processor将其join到一起;star也是如此。

二、itemloader解析数据
ItemLoader 类位于 scrapy.loader ,它可以接收一个 Item 实例来指定要加载的 Item, 然后指定 response 或者 selector 来确定要解析的内容,最后提供了 add_css()、 add_xpath() 方法来对通过 css 、 xpath 解析赋值,还有 add_value() 方法来单独进行赋值

from scrapy.loader import ItemLoader
from douban.items import Movietop250
    def parse_movietop250(self, response):
    '''
    抓取豆瓣电影top250
    '''
    print(response.url)
    #scrapy shell分析xpath语法
    #from scrapy.shell import inspect_response
    #inspect_response(response, self)
    for eachitem in response.xpath('//div[@class="item"]'):
        movie = ItemLoader(item=Movietop250(), selector=eachitem)
        movie.add_xpath('rank', 'div[@class="pic"]/em/text()')
        #movie.add_xpath('title', 'div[@class="pic"]/a/img/@alt')
        movie.add_xpath('title', 'div[@class="info"]/div[@class="hd"]/a/span/text()')
        movie.add_xpath('link', 'div[@class="pic"]/a/@href')
        #movie.add_xpath('star', 'div[@class="info"]/div[@class="bd"]/div[@class="star"]/span[@class="rating_num"]/text()')
        movie.add_xpath('star', 'div[@class="info"]/div[@class="bd"]/div[@class="star"]/span/text()')
        movie.add_xpath('quote', 'div[@class="info"]/div[@class="bd"]/p[@class="quote"]/span[@class="inq"]/text()')
        yield movie.load_item()

    next_page = response.xpath('//div[@class="paginator"]/span[@class="next"]/a/@href').extract_first()
    if next_page:
        yield response.follow(next_page, meta={"cookiejar":response.meta["cookiejar"]}, callback=self.parse_movietop250)

注意:
ItemLoader 默认都会返回一个 list。在之前的方式中我们都是通过 extract_first() 获取第一个值或者通过 extract() 解析到值后进行遍历的。在 ItemLoader 中,为我们提供了 input_processor 和output_processor 来对数据的输入与输出进行解析。其中output_processor提供了Join, MapCompose, TakeFirst等方法。
如:add_xpath接收的参数是list类型的div[@class=”info”]/div[@class=”hd”]/a/span/text(),然后
由output_processor将list中的数据join到一起,再赋值给field。

三、完整代码及爬取数据
完整代码还是在登陆豆瓣的基础上进行抓取,登陆
豆瓣代码请参看scrapy爬虫之模拟登录豆瓣

# -*- coding: utf-8 -*-
#登陆豆瓣,学习使用itme/item_loader,抓取豆瓣电影top250
import scrapy
import urllib
from PIL import Image
from scrapy.loader import ItemLoader
from douban.items import Movietop250


class Movietop250Spider(scrapy.Spider):
    name = 'movieTop250'
    allowed_domains = ['douban.com']
#    start_urls = ['http://www.douban.com/']
    headers = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0"}

    def start_requests(self):
    '''
    重写start_requests,请求登录页面
    '''
    return [scrapy.FormRequest("https://accounts.douban.com/login", headers=self.headers, meta={"cookiejar":1}, callback=self.parse_before_login)]

    def parse_before_login(self, response):
    '''
    登录表单填充,查看验证码
    '''
    print("登录前表单填充")
    captcha_id = response.xpath('//input[@name="captcha-id"]/@value').extract_first()
    captcha_image_url = response.xpath('//img[@id="captcha_image"]/@src').extract_first()
    if captcha_image_url is None:
        print("登录时无验证码")
        formdata = {
                "source": "index_nav",
                "form_email": "aaa@qq.com",
                #请填写你的密码
                "form_password": "*******",
            }
    else:   
        print("登录时有验证码")
        save_image_path = "/home/yanggd/python/scrapy/douban/douban/spiders/captcha.jpeg"
        #将图片验证码下载到本地
        urllib.urlretrieve(captcha_image_url, save_image_path)
        #打开图片,以便我们识别图中验证码
        try:
            im = Image.open('captcha.jpeg')
            im.show()
        except:
            pass

        #手动输入验证码
        captcha_solution = raw_input('根据打开的图片输入验证码:')       
        formdata = {
                "source": "None",
                "redir": "https://www.douban.com",
                "form_email": "aaa@qq.com",
                #请填写你的密码
                                "form_password": "*******",
                "captcha-solution": captcha_solution,
                "captcha-id": captcha_id,
                "login": "登录",
            }


    print("登录中")    
    #提交表单
    return scrapy.FormRequest.from_response(response, meta={"cookiejar":response.meta["cookiejar"]}, headers=self.headers, formdata=formdata, callback=self.parse_after_login)

    def parse_after_login(self, response):
    '''
    验证登录是否成功
    '''
    account = response.xpath('//a[@class="bn-more"]/span/text()').extract_first()
    if account is None:
        print("登录失败") 
    else:
        print(u"登录成功,当前账户为 %s" %account)
        print("进入豆瓣电影top250页面")
        yield scrapy.Request('https://movie.douban.com/top250', meta={"cookiejar":response.meta["cookiejar"]}, headers=self.headers, callback=self.parse_movietop250)

    def parse_movietop250(self, response):
    '''
    抓取豆瓣电影top250
    '''
    print(response.url)
    #scrapy shell分析xpath语法
    #from scrapy.shell import inspect_response
    #inspect_response(response, self)
    for eachitem in response.xpath('//div[@class="item"]'):
        movie = ItemLoader(item=Movietop250(), selector=eachitem)
        movie.add_xpath('rank', 'div[@class="pic"]/em/text()')
        #movie.add_xpath('title', 'div[@class="pic"]/a/img/@alt')
        movie.add_xpath('title', 'div[@class="info"]/div[@class="hd"]/a/span/text()')
        movie.add_xpath('link', 'div[@class="pic"]/a/@href')
        #movie.add_xpath('star', 'div[@class="info"]/div[@class="bd"]/div[@class="star"]/span[@class="rating_num"]/text()')
        movie.add_xpath('star', 'div[@class="info"]/div[@class="bd"]/div[@class="star"]/span/text()')
        movie.add_xpath('quote', 'div[@class="info"]/div[@class="bd"]/p[@class="quote"]/span[@class="inq"]/text()')
        yield movie.load_item()

    next_page = response.xpath('//div[@class="paginator"]/span[@class="next"]/a/@href').extract_first()
    if next_page:
        yield response.follow(next_page, meta={"cookiejar":response.meta["cookiejar"]}, callback=self.parse_movietop250)

运行爬虫并将输出保存为json

scrapy crawl movieTop250 -o movietop250.json
vim movietop250.json
[
{"quote": ["希望让人*。"], "link": ["https://movie.douban.com/subject/1292052/"], "star": "9.6,941921人评价", "rank": ["1"], "title": "肖申克的救赎  / The Shawshank Redemption  / 月黑高飞(港)  /  刺激1995(台)"},
{"quote": ["风华绝代。"], "link": ["https://movie.douban.com/subject/1291546/"], "star": "9.5,682519人评价", "rank": ["2"], "title": "霸王别姬  / 再见,我的妾  /  Farewell My Concubine"},
{"quote": ["怪蜀黍和小萝莉不得不说的故事。"], "link": ["https://movie.douban.com/subject/1295644/"], "star": "9.4,892822人评价", "rank": ["3"], "title": "这个杀手不太冷  / Léon  / 杀手莱昂  /  终极追杀令(台)"},
{"quote": ["一部美国近现代史。"], "link": ["https://movie.douban.com/subject/1292720/"], "star": "9.4,759287人评价", "rank": ["4"], "title": "阿甘正传  / Forrest Gump  / 福雷斯特·冈普"},
{"quote": ["最美的谎言。"], "link": ["https://movie.douban.com/subject/1292063/"], "star": "9.5,443739人评价", "rank": ["5"], "title": "美丽人生  / La vita è bella  / 一个快乐的传说(港)  /  Life Is Beautiful"},
{"quote": ["最好的宫崎骏,最好的久石让。 "], "link": ["https://movie.douban.com/subject/1291561/"], "star": "9.2,709397人评价", "rank": ["6"], "title": "千与千寻  / 千と千尋の神隠し  / 神隐少女(台)  /  Spirited Away"},
{"quote": ["拯救一个人,就是拯救整个世界。"], "link": ["https://movie.douban.com/subject/1295124/"], "star": "9.4,406435人评价", "rank": ["7"], "title": "辛德勒的名单  / Schindler's List  / 舒特拉的名单(港)  /  辛德勒名单"},
{"quote": ["失去的才是永恒的。 "], "link": ["https://movie.douban.com/subject/1292722/"], "star": "9.2,700354人评价", "rank": ["8"], "title": "泰坦尼克号  / Titanic  / 铁达尼号(港 / 台)",
...........
}
]