Scrapy初窥:爬一个绅士动漫站

最近逛到一个绅士动漫视频站(http://hentaidude.com ),发现它的视频是直接以完整的视频文件方式提供的,试了下可以直接下载,于是我就想试着用爬虫抓取下来。尝试了下使用 python 爬虫框架 scrapy,很容易就撸好了,对方没有做反爬虫处理,直接抓就完事了。
我要抓取的网站是典型的分页显示的网站,涉及列表页和详情页,从列表页获取封面图、文件名等信息以及详情页的链接,再逐个抓取详情页获取具体的视频文件地址,附上 github 链接:qqjt/hdude

scrapy 安装与项目初始化

  • 在 pycharm 里直接创建新项目(hdude),顺便弄好 python3 虚拟环境:
    pycharm new project
    这里我本来想把 ubuntu 里的python3.6 升级到 3.7 的,卸载的时候用了purge,发现直接把桌面环境整个卸载了,幸好使用 apt install gnome 重新装回来了,一波操作差点窒息。
  • 初始化 python 虚拟环境的时候出错:No module named 'distutils.core',apt 装个软件包解决:sudo apt install python3-setuptools
  • 激活虚拟环境:source venv/bin/activate
  • 安装scrapy:pip install scrapy,出现Python.h: No such file or directory错误,再装个软件包解决 sudo apt install python3-dev
  • 安装好scrapy 之后初始化项目:scrapy startproject hdude,一个 scrapy 项目就自动生成了。

爬虫代码及说明

hdude/settings.py 里加了俩条配置,,指定下抓取结果的保存方式:

FEED_URI = u'./animes.csv'
FEED_FORMAT = 'CSV'

hdude/items.py 里,指定抓取结果的字段:

from scrapy import Field, Item

class HdudeItem(Item):
    name = Field()
    url = Field()
    like_count = Field()
    view_count = Field()
    cover_image = Field()
    tags = Field()
    source = Field()
    sources = Field()

新建爬虫 spiders/hdude_spider.py,里面是抓取的主要处理:

import scrapy
from scrapy.spiders import CrawlSpider
from hdude.items import HdudeItem
import re

class HdudeSpider(CrawlSpider):
    name = 'hdude'  #爬虫名
    start_urls = ['http://hentaidude.com/'] #开始url

    def parse(self, response):
        self.log('parsing index page: %s' % response.url)
        anime_list = response.xpath('//section[@id="content"]/div[@class="videoPost"]')
        for anime in anime_list:
            item = HdudeItem()
            item['name'] = anime.xpath('a[@class="videoLink"]/text()').extract_first()
            item['url'] = anime.xpath('a[@class="videoLink"]/@href').extract_first()
            item['cover_image'] = anime.xpath('a[@class="thlink"]/div/img/@src').extract_first()
            item['like_count'] = anime.xpath('a[@class="heartLink"]/text()').extract_first()
            item['view_count'] = anime.xpath('*[@class="thumbViews"]/text()').extract_first()
            if item['url']:
                # 抓取详情页,并将列表页面获取的 item 信息用 meta 传递过去
                yield scrapy.Request(item['url'], callback=self.parse_content, meta={'item': item})
        # 继续抓取下个分页
        next_page = response.xpath('//div[@class="navigation"]/ul/li/a[contains(text(),"Next >")]/@href')\
            .extract_first()
        if next_page:
            yield scrapy.Request(next_page, callback=self.parse)

    def parse_content(self, response):
        # 详情页面提取视频地址和标签
        self.log('parsing detail page: %s' % response.url)
        item = response.meta['item']
        sources = re.findall(r'sources\[\'video-source-[0-9]\'\] = \'(.*?)\';',
                             response.xpath('//body').extract_first())

        if sources:
            sources = [source for source in sources if source.startswith('http')]   # 过滤掉 iframe 视频源
            item['source'] = sources[0]
            item['sources'] = '|'.join(sources)
        tags = response.xpath('//div[@class="new-tags"]/a/text()').extract()
        if tags:
            item['tags'] = ','.join(tags)
        yield item

其中 parse() 方法解析列表页,parse_content()方法解析详情页。scrapy 自带 xpath 可用来获取页面元素,在获取具体的视频文件地址时,由于播放控件是 js 渲染出来的,需要利用正则表达式从 javascript 代码里提取。
yield 是很妙的东东,利用 yield scrapy.Request() 很方便地进行下一步抓取, yield item 就是输出抓取结果,简洁方便,同时利用yield 可以实现异步执行。

抓取及后续处理

在爬虫目录(不是项目根目录)下执行 scrapy crawl hdude 命令执行爬虫,完事后从csv文件内复制出视频地址,放到下载工具里下载就是了,大概九百多个视频(╰_╯)。

附上抓取结果: animes.csv

参考资料