python爬虫写入mysql_从零开始写Python爬虫 --- 2.6 爬虫实践:重构排行榜小说爬虫&Mysql数据库...

这次我们要在scrapy框架下重构我们上次写的排行榜小说爬虫(https://zhuanlan.zhihu.com/p/26756909) 并将爬取的结果存储到mysql数据库中。另外,这是爬虫专栏第二部分:Scrapy框架 的最后一篇文章啦~

目标分析:

我们的目标十分明确:

由于上次自己写的bs4小说爬虫效率堪忧,

我又不肯自己写多线程(其实是不会!逃)

所以我们来利用Scrapy强大的并发功能吧!

但是,用到并发其实会有个坑,下文会着重说明。

那么我们只要:

找到小说每一章的链接地址,

将每章小说的标题、正文部分存入数据库。

数据筛选:

由于是代码的重构,其实在上次的文章中我们就已经把整个爬虫如何运作的逻辑完成了,这次只需要用Scrapy框架的方法重写一遍就行。另外,也会抛弃bs4库,投降Xpath的怀抱。

好来,我们来看具体怎么写吧。

遇到的麻烦:

说起来,一开始我是不肯用数据库的,

我觉得直接把小说爬下来写入文本不就结了吗?

然而,理想很丰满 现实很骨干!

写入的文本是这样的:

发现了没有? 小说的顺序是不固定的,序章之后居然就是65章了。

我去查了一下原因:scrapy异步处理Request请求,Scrapy发送请求之后,不会等待这个请求的响应,他会同时发送其他请求或者做别的事情。

整数因为这个特性,Scrapy才能有这么快的速度,他在cpu等待IO操作的时间,发起了一个新的线程。

如何解决?

遇到关于顺序这个蛋疼的问题,我想了很多办法:

比如设置request的priority(优先级),让每次request按照优先级排队发出,然而这样并不能改变写进文本的顺序。

在思考一番之后,我决定通过将数据写入mysql数据库,来解决排序的问题:

当然,还是由于Scrapy的并发系统,就算是写入数据库,也不能按顺序入库,

结果是这样的:

我拍脑袋一想,为什么要按顺序入库呢,

我在查询数据的时候,给他按章节名来排序不就结了?

我真机智,快为我点个赞!

然而,如果按照章节名排序,出来的结果是:

是的 mysql是瑞典人开发的,

默认是utf8编码,

不支持中文排序也是很正常的!

这里我们是不是陷入了trouble呢?

当然不是,我们给每章小说都定义一个id字段,

最后通过id来给章节排序,不就完了吗?

这里我选择:

将每一章的章节名中的数字,

转换为阿拉伯数字,

再传入id字段~

看代码吧:

注意,需要将这个模块 放在和spider同级目录,方便一会我们写spider的时候导入

'''实现了中文向阿拉伯数字转换用于从小说章节名提取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('第一千三百九十一章 你妹妹被我咬了!')))

解决了用于排序的id的问题,我们就可以开始写代码了

项目的创建:

# 创建项目

scrapy startproject biquge

# 进入文件夹

cd biquge

# 生成爬虫文件

scrapy genspider xsphspider

# 看一下目录树:

.

├── biquge

│ ├── __init__.py

│ ├── __pycache__

│ │ ├── __init__.cpython-36.pyc

│ │ ├── items.cpython-36.pyc

│ │ ├── pipelines.cpython-36.pyc

│ │ └── settings.cpython-36.pyc

│ ├── items.py

│ ├── middlewares.py

│ ├── pipelines.py

│ ├── settings.py

│ └── spiders

│ ├── __init__.py

│ ├── __pycache__

│ │ ├── __init__.cpython-36.pyc

│ │ ├── sjzh.cpython-36.pyc

│ │ └── xsphspider.cpython-36.pyc

│ ├── sjzh.py

│ └── xsphspider.py

└── scrapy.cfg

编写Items:

还是和原来一样,我们先定义好没一个爬取的item(小说章节)有哪些字段是我们需要的:

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:

links = book.xpath('.//div[2]/div[2]/ul/li')

for link in links:

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:

mysql数据库:

由于这里我们需要将数据写入Mysql数据库,这里需要自己有一点mysql基本操作的知识:

如果对于数据库一点都不懂,这里有一本比较好的教程,跟着做一遍,大体就都明白了。

具体代码:

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%svalues (%d,\'%s\',\'%s\')' % (

name, order_id, title, body)

cursor.execute(sql1)

cursor.execute(sql)

# 提交本次插入的记录

connection.commit()

finally:

# 关闭连接

connection.close()

return item

配置settings:

将我们写的PIPELINE加入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

结果展示:

由于没有爬太长时间,我就关闭掉了,就爬了一点点:

# 登录mysql数据库

mysql -uroot -p

# 选中小说数据库

use bqgxiaoshuo;

# 查看爬到的小说L

show tables;

看一下小说章节的排序:

select zjm from "小说名" order by id;

可以看到已经基本完成排序的工作了。

当然,这个爬虫只是初步实现了基本功能,

实际上还有很多bug和需要优化的地方,

这些大家可以自己在源代码的基础上添加功能啦,

如果有人能基于这个真的做出一个小说阅读app那就更好了!

到这里,我们的Scrapy爬虫的学习记录就要告一段落了,

从下一篇文章开始,我将会介绍模拟浏览器爬虫~

每天的学习记录都会 同步更新到:

微信公众号: findyourownway

知乎专栏:从零开始写Python爬虫 - 知乎专栏

blog : www.ehcoblog.ml

Github: Ehco1996/Python-crawler

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值