添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
爬虫小记——曾经热爱的漫画

爬虫小记——曾经热爱的漫画

火影忍者已经完结,但我还念念不忘。

省下一次次早餐钱、忍饥挨饿在所不惜、只敢偷摸着看的热爱

和小伙伴们攒钱"众筹"买来你我分享的快乐时光

那个躲在被窝里被手电筒照亮的热血世界

都已逝去,但不曾失去

既为纪念,亦为实践 Scrapy ,故写下此文

系统环境:win10
IDE:pycharm
python : 3.6

关于Scrapy:

简介:Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。其最初是为了 页面抓取 (更确切来说, 网络抓取 )所设计的, 也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services ) 或者通用的网络爬虫。

Scrapy使用前需要安装,网上有介绍。

Scrapy其实本人不是很精通,但是官方文档的初级教程已足够我写出这份爬虫了。

本文所有关于Scrapy 的指令在初级教程都有提及。

先期探索——目标页

目标网址:


1.例行ctrl+F12 审查元素:

目标网页审查元素

发现所有章节的相关信息都包含在节点 <div id = 'content'> 下,且其下所有 <li>节点的子节点<a> 都依次包含了某一章节的 次级url ,章节名。

2.下面尝试获取章节相关性息

这里需要调试,可以使用scrapy 内置的shell,但是太丑了,故使用win10自带的 Power Shell(以下简称PS)

Scrapy shell "https://www.fzdm.com/manhua/001//"

通过这个指令,PS 返回 一个 response ,其中包含了目标网址的全部信息, 其内容就是审查元素中看到的HTTP代码。

也可以通过指令 response.body 来输出HTTP代码, 但这样看的体验极差,不推荐。


回到我们之前提到

<li class = "pure-u-1-2 pure-u-lg-1-4">都包含了某一章节的 次级url ,章节名 ......

故通过 xpath 获取并拼接章节url以及章节名:


可以将xpath看作一种查找工具
关于xpath 下面有更多信息和教程


输入以下指令

response.xpath('//li/a')
查找章节相关信息

提取里面的节点要用到extract()方法

提取节点: response.xpath('//li/a').extract()

提取章节相关节点

可以通过以下指令分别提取章节次级url和章节名:

1. 章节url : response.xpath('//li//a/@href').extract(
2. 章节名 :response.xpath('//li/a/text()').extract()

以上 我们成功获得了全部章节的URL和章节名。


二期探索:章节URL网页

单单获得所有章节的URL还不够,但已经做完了一半。

继续探索章节网页

步骤和之前探索目标网址一样

1.审查元素:

这里以章节“博人传18”为例

章节页:

发现我们需要的信息有:

1.漫画页码:位于节点 <div class = "navigation"> 下的节点<a>中
2.漫画图片url:位于节点 <script>下的节点<img>


2. 尝试提取我们需要的信息

PS退出之前的目标网址( 指令:exit )

PS输入

Scrapy shell "https://www.fzdm.com/manhua/001//brz18/"


2.1查找漫画页码:

response.xpath('//div/a/text()')

结果:

页码

我们需要的是,其中的 "1,2,3,4,5,6,7" 。

发现包含多余信息,但没关系,可以通过正则表达式获得我们需要的页码信息。

response.xpath('//div/a/text()').re('\d')

漫画页码提取成功

2.2提取漫画图片url:

继续输入

response.xpath('//script/img/@scr').extract()
但是

发现返回为空。


回到漫画页观察审查元素发现,这个链接是使用JS动态加载进去的。直接获取是不行的,网页分为静态页面和动态页面,对于静态页面好说,对于动态页面就复杂一些了。可以使用PhantomJS、发送JS请求、使用Selenium、运行JS脚本等方式获取动态加载的内容。

其实回顾一下发现JS脚本就在节点中 <script type="text/javascript"> ,这说明JS脚本并非外部脚本,这样就好处理,可以省下很多时间。:

JS脚本

阅读JS脚本,发现其中已经包含了我们需要的信息,比如说第二行中漫画图片url :

mhurl="2018/08/04144025271276.jpg

以及漫画图片的服务器地址:

http://p3.xiaoshidi.net/

尝试提取下

response.xpath('//script/text()').re('mhurl=+.*\.jpg')

提取漫画URL成功

到此全部探索完成,整理我们的思路:

1.我们需要从目标页中提取所有章节的次级url 和章节名,将得到的章节页次级URL与目标页URL拼接, 得到所有章节的URL

2.之前我们在章节页中发现,每章节都包含了多个漫画页的URL,我们需要从每个章节页中获取所有的漫画页URL

3.从漫画页中获取 漫画图片URL

4.保存图片。

图片的存储规则:每一章节为一个独立文件夹,每章节的漫画图片根据页码命名。

1.Scrapy 项目创建

这部分参照scrapy文档中的初级教程而写的

scrapy 新建爬虫 在POWERSHELL中输入:scrapy startproject 爬虫项目名
我的是: scrapy startproject comic_crawer

  1. 创建好后,在对应目录里,找到对应的项目目录:
项目目录


其中文件的意义:

  • scrapy.cfg : 项目的配置文件
  • comic_crawer/ : 该项目的python模块。之后将在此加入代码。
  • comic_crawer/items.py : 项目中的item文件.
  • comic_crawer/pipelines.py : 项目中的pipelines文件.
  • comic_crawer/settings.py : 项目的设置文件.
  • comic_crawer/spiders/ : 放置spider代码的目录.

2.代码

上面的文件中,需要编写items.py 和 spiders下面的爬虫代码,以及修改settings.py中的一些代码

这部分需要自己理解,已经在代码中加入了注释

items.py 编写

内容如下:

import scrapy
class ComicCrawerItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    chapter_name = scrapy.Field() #章节名[]
    chapter_url = scrapy.Field()  #章节url[]
    index_urls = scrapy.Field() #每章页码链接[]
    indexes = scrapy.Field() #每章的页码[]
    img_url = scrapy.Field()      #每页漫画图片url
    img_paths = scrapy.Field() #漫画图片存储路径。

爬虫呢。爬虫要在comic_crawer/spiders/ 创建自己的python 文件,写入爬虫代码:

comic_spider.py

import re
import scrapy
from scrapy import Selector
from comic_crawer.items import ComicCrawerItem
class ComicSpider(scrapy.Spider):
    #爬虫的名字,自定义,启用爬虫时需要用到
    name = 'Comic'
    def __init__(self):
        #设置爬虫活动范围,防止乱爬
        self.allowed_domains = ['www.fzdm.com']
        #设置初始目标页
        self.start_url = ['https://www.fzdm.com/manhua/1/']
        #章节URL的前半部分,用于和后面获得的次级URL进行拼接,得到章节页URL
        self.target_link = 'https://www.fzdm.com/manhua/1/'
        #漫画图片服务器地址
        self.img_server = 'http://p3.xiaoshidi.net/'
    def start_requests(self):
        #启动时爬虫从start_url开始
        yield scrapy.Request(url =self.start_url[0], callback= self.parse_1)
    def parse_1(self, response):
        items = []
        chapter_response = Selector(response)
        urls = chapter_response.xpath('//li//a/@href').extract()
        #urls 全部章节URL
        chapter_names = chapter_response.xpath('//li/a/text()').extract()
        #chapter_names 全部章节名
        #将获得的信息保存到item, item可以在scrapy的各个组件中传递
        for index in range(len(urls)):
            item = ComicCrawerItem()
            item['chapter_name'] = chapter_names[index]
            item['chapter_url'] = self.target_link + urls[index]
            item['index_urls'] = []
            item['img_url'] = []
            items.append(item)
        for item in items[3: ] :
            #items的前三项不是我们需要的。所以从第四项开始
            # 解析获得章节URL, 并传递item到parse2
            yield scrapy.Request(url= item['chapter_url'], meta= {'item':item}, callback= self.parse_2)
    def parse_2(self, response):
        ####parse_2 主要用于解析章节URL, 获得漫画图片链接和页数信息###
        #接收parse_1传递来的item
        item = response.meta['item']
        img_response = Selector(response)
        #获取图片页数列表
        img_page_list = response.xpath('//div/a/text()').re('\d')
        indexes = range(len(img_page_list))
        #页数列表保存到item中
        item['indexes'] = img_page_list
        for index in indexes :
            #将章节页URL与页数拼接组成漫画页URL
            index_url = item['chapter_url']+'index_'+str(index)+'.html'
            #漫画页URL保存入item中
            item['index_urls'].append(index_url)
            #抓取漫画页,传递item到parse_3
            yield scrapy.Request(url= index_url, meta={'item': item}, callback = self.parse_3)
    def parse_3(self, response):
        ###parse_3用于解析漫画页URL,并获得漫画图片的URL###
        #接受Parse_2传递来的item
        item = response.meta['item']
        index_response = Selector(response)
        # 截掉“mhurl=”,获得漫画图片URL
        img_url_last = response.xpath('//script/text()').re('mhurl=+.*\.jpg')[0][7:]
        #漫画图片服务器地址与漫画图片URL拼接,得到完整URL
        ultimate_img_url = self.img_server+img_url_last
        #保存到item中
        item['img_url'].append(ultimate_img_url)
        #将item传给pipelines, 进行漫画图片的下载和保存
        yield item

settings.py编写:

BOT_NAME = 'comic_crawer'
SPIDER_MODULES = ['comic_crawer.spiders']
NEWSPIDER_MODULE = 'comic_crawer.spiders'
IMAGES_STORE = 'D:/火影忍者'
# USER_AGENTS = [
#     "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
ROBOTSTXT_OBEY = False
DOWNLOAD_DELAY = 0.25
COOKIES_ENABLED = False
DEFAULT_REQUEST_HEADERS = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'
ITEM_PIPELINES = {
    'comic_crawer.pipelines.ComicCrawerPipeline': 1,
}

pipelines.py编写:

from comic_crawer import settings
from scrapy import Request
import requests
import os
class ComicCrawerPipeline(object):
    def process_item(self, item, spider):
        if 'img_url' in item:
            image_urls = item['img_url']
            indexes = item['indexes']
            img_paths = []
            #文件夹路径及名称
            chapter_path = '%s/%s' % (settings.IMAGES_STORE, item['chapter_name'])
            #若不存在则创建
            if not os.path.exists(chapter_path):
                os.makedirs(chapter_path)
            for (image_url,index) in zip(image_urls,indexes):
                img_name = '第' + index + '页.jpg'
                #图片保存路径
                img_path = '%s/%s' % (chapter_path, img_name)
                img_paths.append(img_path)
                with open(img_path,'wb') as f_img:
                    response = requests.get(url= image_url)
                    for block in response.iter_content(1024):
                        if not block:
                            break