scrapy 动态网页处理——爬取鼠绘海贼王最新漫画

2020-10-19更新:鼠绘网已关闭。

简介

scrapy是基于python的爬虫框架,易于学习与使用。本篇文章主要介绍如何使用scrapy爬取鼠绘漫画网海贼王最新一集的漫画。

源码参见:https://github.com/luoheng23/projects/tree/master/scrapy/crawlComics

网站分析

鼠绘海贼王网站网址为:http://www.ishuhui.com/comics/anime/1

漫画链接无法直接从原始网页中得到,需要点击对应的话数,链接才会显示出来,如下图所示:

获取链接后即可获得海贼王漫画的网页地址,网页如下:

原始的网页没有漫画的图片链接,需要点击图中的“连页模式”,之后网页才会显示漫画的具体内容,此时的漫画内容也是动态加载的,随着窗口不断往下,图片会一张张加载出来。

工具

scrapy: 爬虫框架

splash: 动态网页处理

环境安装

scrapy安装

已安装anaconda,python3.7版本。

创建scrapy虚拟环境并激活

1 conda create -n scrapy
2 conda activate scrapy

使用pip安装scrapy以及用于与splash交互的scrapy-splash

1 pip install scrapy scrapy-splash

在使用scrapy的ImagePipeline的时候需要第三方库PIL,安装PIL

1 pip install pillow

splash安装

splash服务是通过docker启用的,所以要先安装docker,docker安装参见:https://docs.docker.com/install/

使用docker安装splash

1 docker pull scrapinghub/splash

启用splash服务

1 docker run -p 8050:8050 scrapinghub/splash

scrapy爬虫

生成scrapy项目并生成爬虫

1 scrapy startproject crawlComics
2 cd crawlComics
3 scrapy genspider shuhui http://www.ishuhui.com/anime/1

此时在spiders文件夹下有了shuhui.py,这是爬虫的基础模板。

修改基础设置(setting.py)

使用splash的设定

 1  # Splash服务器地址
 2  SPLASH_URL = 'http://localhost:8050'
 3  # 开启Splash的两个下载中间件并调整HttpCompressionMiddleware的次序
 4  DOWNLOADER_MIDDLEWARES = {
 5      'scrapy_splash.SplashCookiesMiddleware': 723,
 6      'scrapy_splash.SplashMiddleware': 725,
 7      'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 727,
 8  }
 9  # 设置去重过滤器
10  DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'

user-agent

1 USER_AGENT = "Mozilla/5.0 (Platform; Encryption; OS-or-CPU; Language) AppleWebKit/AppleWebKitVersion (KHTML, like Gecko)"

使用ImagePipelines保存图片的相关设定

1 # 图片存储位置
2 IMAGES_STORE = "/home/luoheng/comics/"
3 # 启用ImagesPipeline来下载图片
4 ITEM_PIPELINES = {
5     'crawlComics.pipelines.RenameImagesPipeline': 500, 
6 }

添加item

item是scrapy中传输数据的基本单位,可以简单的理解为字典。

scrapy的ImagePipelines是为下载图片专门设计的,它接受一个包含键值“image_urls”和“images”的item。ImagePipelines在得到item后,会自动根据image_urls中包含的链接下载对应的图片,并放置到上文设定的IMAGE_STORE中。

故为了使用ImagePipelines,在items.py中添加一个符合条件的item:

1 class ImageItem(scrapy.Item):
2     image_urls = scrapy.Field()
3     images = scrapy.Field()
4     # store picture names
5     image_names = scrapy.Field()

前两项是为了满足ImagePipelines的要求设置的,最后一项用于设置下载图片的名字,因为ImagePipelines默认使用hash值对图片进行命名,不具可读性。

实现爬虫

爬虫主要的工作流程

1. 在http://www.ishuhui.com/anime/1页面点击最新漫画按钮,返回响应结果

2. 从响应结果中获取海贼王最新漫画的具体网址

3. 在最新漫画的网址中点击“连页模式”,并将网页拉到最下方,以使所有图片的链接加载出来,返回响应结果

4. 从响应结果中获取图片的链接,使用ImagePipelines下载

导入item与splash

1 from scrapy_splash import SplashRequest
2 from ..items import ImageItem

SplashRequest用于与splash服务进行交互,在这里主要用了splash的“execute”功能,它接受的几个参数如下:

1 SplashRequest(url, callback=self.parse, endpoint="execute", args={"lua_source": lua_script})

url是原始网址,callback是回调函数,即处理返回的response的函数,endpoint指明使用的功能,这里使用的是“execute”,即执行脚本的功能,最后一个args用于指定用于执行的脚本,这里的脚本是用lua语言写成的,在Python中以字符串形式存在。

点击最新漫画

 1 click_latest_comic = """
 2 function main(splash)
 3     # 打开网页
 4     splash:go(splash.args.url)
 5     # 等待网页加载
 6     splash:wait(2)
 7     # 点击最新漫画(由class ant-tag-red识别)
 8     splash:runjs("document.getElementsByClassName('ant-tag-red')[0].click()")
 9     # 等待网页加载
10     splash:wait(0.1)
11     # 返回网页结果
12     return splash:html()
13 end
14 """
15 # 返回点击最新漫画后的响应结果
16 yield SplashRequest(url, callback=self.show_all, endpoint="execute", args={"lua_source": click_latest_comic})

打开具体网址,并加载所有漫画图片

show_all_pictures = """
function main(splash)
    # 打开网页
    splash:go(splash.args.url)
    # 等待网页加载
    splash:wait(2)
    # 点击连页模式(由class z-page识别)
    splash:runjs("document.getElementsByClassName('z-page')[0].click()")
    # 等待网页加载
    splash:wait(0.1)
    # 将网页拉到最下方,使所有图片的链接加载出来
    splash:runjs("window.scrollTo(0, document.body.scrollHeight)")
    # 等待网页加载
    splash:wait(0.1)
    # 返回响应结果
    return splash:html()
end
"""

def show_all(self, response):
    # 获取漫画具体网址
    target = response.css(".m-comics-num-link").xpath("@url").extract_first()
    # target是相对网址,它的前缀是http://www.hanhuazu.cc
    new_url = "http://www.hanhuazu.cc" + target
    # 返回已经加载了所有图片链接的网页
    yield SplashRequest(new_url, endpoint="execute", args={"lua_source": show_all_pictures}) 

使用ImageItem保存图片链接并传送给ImagePipelines下载

 1 def parse(self, response):
 2     # 所有图片
 3     comics = response.css("div img")
 4     # src属性中保存所有图片链接,alt属性保存图片名字
 5     comics_picture, comics_name = comics.xpath("@src").extract(), comics.xpath("@alt").extract()
 6     # 构建ImageItem并返回给ImagePipelines
 7     images = ImageItem()
 8     # 保存图片链接
 9     images["image_urls"] = comics_picture
10     # 保存图片链接与图片名字的映射,用于下载图片的命名
11     images["image_names"] = dict(zip(comics_picture, comics_name))
12     # 返回后,ImagePipelines会自动下载图片
13     return images

修改ImagePipelines,覆盖默认命名

 1 class RenameImagesPipeline(ImagesPipeline):
 2     """to rename the images properly"""
 3 
 4 
 5     def process_item(self, item, spider):
 6         # add names
 7         self.item_names = item["image_names"]
 8         info = self.spiderinfo
 9         requests = arg_to_iter(self.get_media_requests(item, info))
10         dlist = [self._process_request(r, info) for r in requests]
11         dfd = DeferredList(dlist, consumeErrors=1)
12         return dfd.addCallback(self.item_completed, item, info)
13 
14     def file_path(self, request, response=None, info=None):
15         return self.item_names[request.url]

ImagesPipeline的file_path用于计算图片的命名,但是由于图片名字保存在item的image_names属性中,而file_path并不直接接受item参数,所以需要将item["image_names"]保存到self.item_names中,如此file_path才可访问到名字。

因此将ImagesPipeline的process_item方法的代码完整复制过来,并在最前面将image_names保存到self.item_names当中,在file_path方法中只用简单调用self.item_names[request.url]即可获取图片名字。

运行爬虫

1 scrapy crawl shuhui

完成后,在comics文件夹中可以看到爬取的图片。

总结

以爬取鼠绘海贼王漫画为例,本文简单介绍了使用scrapy+splash处理动态网页的方法。

 

posted @ 2019-06-05 16:46  luoheng  阅读(1509)  评论(0编辑  收藏  举报