Scrapy应用(四)

坑:抄完代码准备开爬时,出现了目标计算机积极拒绝balabala的错误,查了半天也没解决,就放弃了。结果在用pip 安装时也报了这个错突然想起来我之前也遇见过这个错误。是由于用过vpn导致的。解决办法是打开控制面板/网络和Internet/Internet选项/连接/局域网设置/把代理服务器×了就ok了

eg.方便数据库排序
在spiders文件夹下建立sjzh.py

'''
实现了中文向阿拉伯数字转换
用于从小说章节名提取id来排序
'''

chs_arabic_map = {'零': 0, '一': 1, '二': 2, '三': 3, '四': 4,
                  '五': 5, '六': 6, '七': 7, '八': 8, '九': 9,
                  '十': 10, '百': 100, '千': 10 ** 3, '万': 10 ** 4,
                  '〇': 0, '壹': 1, '贰': 2, '叁': 3, '肆': 4,
                  '伍': 5, '陆': 6, '柒': 7, '捌': 8, '玖': 9,
                  '拾': 10, '佰': 100, '仟': 10 ** 3, '萬': 10 ** 4,
                  '亿': 10 ** 8, '億': 10 ** 8, '幺': 1,
                  '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5,
                  '7': 7, '8': 8, '9': 9}

num_list = ['1','2','4','5','6','7','8','9','0','一','二','三','四','五','六','七','八','九','十','零','千','百',]

def get_tit_num(title):
    result =''
    for char in title:
        if char in num_list:
            result+=char
    return result


def Cn2An(chinese_digits):

    result = 0
    tmp = 0
    hnd_mln = 0
    for count in range(len(chinese_digits)):
        curr_char = chinese_digits[count]
        curr_digit = chs_arabic_map[curr_char]
        # meet 「亿」 or 「億」
        if curr_digit == 10 ** 8:
            result = result + tmp
            result = result * curr_digit
            # get result before 「亿」 and store it into hnd_mln
            # reset `result`
            hnd_mln = hnd_mln * 10 ** 8 + result
            result = 0
            tmp = 0
        # meet 「万」 or 「萬」
        elif curr_digit == 10 ** 4:
            result = result + tmp
            result = result * curr_digit
            tmp = 0
        # meet 「十」, 「百」, 「千」 or their traditional version
        elif curr_digit >= 10:
            tmp = 1 if tmp == 0 else tmp
            result = result + curr_digit * tmp
            tmp = 0
        # meet single digit
        elif curr_digit is not None:
            tmp = tmp * 10 + curr_digit
        else:
            return result
    result = result + tmp
    result = result + hnd_mln
    return result

# test
print (Cn2An(get_tit_num('第一千三百九十一章 你妹妹被我咬了!')))

老规矩先建项目和spider

创建项目
scrapy startproject biquge
进入文件夹
cd biquge
生成爬虫文件
scrapy genspider xsphspider qu.la

编写items

import scrapy

class BiqugeItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    # 小说名字
    bookname = scrapy.Field()
    #章节名
    title = scrapy.Field()
    #正文
    body  = scrapy.Field()
    #排序用id
    order_id = scrapy.Field()

编写spider

由于我们的spider爬取顺序是这样的:

首先: 爬取排行榜页面,找到每一本小说的页面
接着: 爬取小说页面, 找到小说每一章的链接
最后: 爬取每一章节页面,找到文章标题和正文内容

我们再来复习一下 spider是怎么运作的:

首先: 从start_urls里发起请求,返回response
接着: 自动调用 parse函数
中间: 一系列我们自己添加的功能
最后: 返回item,给PIPELINE处理

为了实现我们定好的spider逻辑,我们得调用Scrapy内置的requests函数,
来介绍一下Scrapy.request函数:

class Request(url, callback=None, method='GET', headers=None, body=None, cookies=None, meta=None, encoding='utf-8', priority=0, dont_filter=False, errback=None)
# 这里其实和我们一直用的request模块也差不多,最主要需要注意的参数:
# callback 这个参数的意思是回调函数,就是会自动运行的函数,并将request获得的response自动传进去。
# -*- coding: utf-8 -*-
import scrapy
from biquge.items import BiqugeItem
# 导入我们自己写的函数
from .sjzh import Cn2An,get_tit_num


class XsphspiderSpider(scrapy.Spider):
    name = "xsphspider"
    allowed_domains = ["qu.la"]
    start_urls = ['http://www.qu.la/paihangbang/']
    novel_list = []

    def parse(self, response):

        # 找到各类小说排行榜名单
        books = response.xpath('.//div[@class="index_toplist mright mbottom"]')

        # 找到每一类小说排行榜的每一本小说的下载链接
        for book in books[0:1]:
            links = book.xpath('.//div[2]/div[2]/ul/li')
            for link in links[0:2]:
                url = 'http://www.qu.la' + \
                    link.xpath('.//a/@href').extract()[0]
                self.novel_list.append(url)

        # 简单的去重
        self.novel_list = list(set(self.novel_list))

        for novel in self.novel_list:
           yield scrapy.Request(novel, callback=self.get_page_url)

    def get_page_url(self, response):
        '''
        找到章节链接
        '''
        page_urls = response.xpath('.//dd/a/@href').extract()

        for url in page_urls:
           yield scrapy.Request('http://www.qu.la' + url,callback=self.get_text)

    def get_text(self, response):
        '''
        找到每一章小说的标题和正文
        并自动生成id字段,用于表的排序
        '''
        item = BiqugeItem()

        # 小说名
        item['bookname'] = response.xpath(
            './/div[@class="con_top"]/a[2]/text()').extract()[0]

        # 章节名 ,将title单独找出来,为了提取章节中的数字
        title = response.xpath('.//h1/text()').extract()[0]
        item['title'] = title

        #  找到用于排序的id值
        item['order_id'] = Cn2An(get_tit_num(title))

        # 正文部分需要特殊处理
        body = response.xpath('.//div[@id="content"]/text()').extract()

        # 将抓到的body转换成字符串,接着去掉\t之类的排版符号,
        text = ''.join(body).strip().replace('\u3000', '')
        item['body'] = text
        return item

编写pipeline处理爬到的数据

import pymysql

 class BiqugePipeline(object):
    def process_item(self, item, spider):
        '''
        将爬到的小数写入数据库
        '''

        # 首先从items里取出数据
        name = item['bookname']
        order_id = item['order_id']
        body = item['body']
        title = item['title']

        # 与本地数据库建立联系
        # 和本地的scrapyDB数据库建立连接
        connection = pymysql.connect(
            host='localhost',  # 连接的是本地数据库
            user='root',        # 自己的mysql用户名
            passwd='********',  # 自己的密码
            db='bqgxiaoshuo',      # 数据库的名字
            charset='utf8mb4',     # 默认的编码方式:
            cursorclass=pymysql.cursors.DictCursor)

        try:
            with connection.cursor() as cursor:
                # 数据库表的sql
                sql1 = 'Create Table If Not Exists %s(id int,zjm varchar(20),body text)' % name
                # 单章小说的写入
                sql = 'Insert into %s values (%d ,\'%s\',\'%s\')' % (
                    name, order_id, title, body)
                cursor.execute(sql1)
                cursor.execute(sql)

            # 提交本次插入的记录
            connection.commit()
        finally:
            # 关闭连接
            connection.close()
            return item

配置settings

ITEM_PIPELINES = {
    'biquge.pipelines.BiqugePipeline': 300,
}

中断后恢复任务
由于这次我们需要爬得数据量非常的大,
就算有强大的多线程也不是一时半会就能爬完的,
所以这里我们得知道如果爬虫爬到一半断了,我们如何从断的地方接着工作,
而不是从头开始

Job 路径

要启用持久化支持,你只需要通过 JOBDIR 设置 job directory 选项。这个路径将会存储 所有的请求数据来保持一个单独任务的状态(例如:一次spider爬取(a spider run))。必须要注意的是,这个目录不允许被不同的spider 共享,甚至是同一个spider的不同jobs/runs也不行。也就是说,这个目录就是存储一个 单独 job的状态信息。

如何使用?

要启用一个爬虫的持久化,运行以下命令:

scrapy crawl somespider -s JOBDIR=crawls/somespider-1

然后,你就能在任何时候安全地停止爬虫(按Ctrl-C或者发送一个信号)。
恢复这个爬虫也是同样的命令:

scrapy crawl somespider -s JOBDIR=crawls/somespider-1

战术总结
爬取过程中出现DEBUG: Redirecting (301) to <GET ht。。。。。。是正常现象不会错过一些网页,原作者sjzh.py设置数据库id的代码有小bug


   转载规则


《Scrapy应用(四)》 刘坤胤 采用 知识共享署名 4.0 国际许可协议 进行许可。