前言

Python刚接触,基础不好,都是现拉过来直接搞爬虫,脑子笨,边学边记录。这里使用Scrapy框架,相关信息需自行百度。
这里是在【《Python笔记》Scrapy爬虫(1)本地存储】的代码基础,并需要满足下面几个条件

使用Scrapy框架,需要提前下载好第三方库目标:

  1. 本地安装所需要的各种环境,按照自己需要的安装
  2. 创建scrapy爬虫项目
  3. 爬取正确的数据
  4. 对爬取的数据进行格式转换
  5. 爬取的数据进行本地存储(存入txt文…

目标:

  1. 爬取正确的数据
    (1) 对爬取的数据进行格式转换
    (2) 根据自定义规则,拆分标题、章节数
  2. 爬取多个章节,支持分页,下一页爬取
  3. 爬取的数据存入数据库
    (1) 新建数据库
    (2) settings.py中配置数据连接信息
    (3) 引入已经写好的mysql.py
    (4) 判断数据库是否存在,避免爬取的数据重复
    (5) 新数据存库

引入 mysql.py

我这里直接引用别人写好的 mysql.py 数据库连接工具,也可以自己手写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
"""
数据库工具类
# """
import pymysql
import traceback
from DBUtils.PooledDB import PooledDB
from scrapy.utils.project import get_project_settings


class MysqlUtil(object):

# 获取setting文件中的配置
settings = get_project_settings()

config = {
'host': settings.get('MYSQL_HOST'),
'port': settings.get('MYSQL_PORT'),
'database': settings.get('MYSQL_DATABASE'),
'user': settings.get('MYSQL_USER'),
'password': settings.get('MYSQL_PASSWORD'),
'charset': settings.get('MYSQL_CHARSET')
}

"""
MYSQL数据库对象,负责产生数据库连接 , 此类中的连接采用连接池实现获取连接对象:conn = Mysql.getConn()
释放连接对象;conn.close()或del conn
"""
# 连接池对象
__pool = None

def __init__(self):
# 数据库构造函数,从连接池中取出连接,并生成操作游标
self._conn = MysqlUtil.get_conn()
self._cursor = self._conn.cursor()

# 获取链接
@staticmethod
def get_conn():
"""
@summary: 静态方法,从连接池中取出连接
@return MySQLdb.connection
"""
if MysqlUtil.__pool is None:
__pool = PooledDB(creator=pymysql, mincached=1, maxcached=100, host=MysqlUtil.config['host'], port=MysqlUtil.config['port'], user=MysqlUtil.config['user'], passwd=MysqlUtil.config['password'], db=MysqlUtil.config['database'], charset='utf8')
return __pool.connection()

# 查询所有数据
def get_all(self, sql, param=None):
"""
@summary: 执行查询,并取出所有结果集
@param sql:查询SQL,如果有查询条件,请只指定条件列表,并将条件值使用参数[param]传递进来
@param param: 可选参数,条件列表值(元组/列表)
@return: result list(字典对象)/boolean 查询到的结果集
"""
try:
if param is None:
count = self._cursor.execute(sql)
else:
count = self._cursor.execute(sql, param)
if count > 0:
result = self._cursor.fetchall()
else:
result = False
return result
except Exception as e:
traceback.print_exc(e)

# 查询某一个数据
def get_one(self, sql, param=None):
"""
@summary: 执行查询,并取出第一条
@param sql:查询SQL,如果有查询条件,请只指定条件列表,并将条件值使用参数[param]传递进来
@param param: 可选参数,条件列表值(元组/列表)
@return: result list/boolean 查询到的结果集
"""
try:
if param is None:
count = self._cursor.execute(sql)
else:
count = self._cursor.execute(sql, param)
if count > 0:
result = self._cursor.fetchone()
else:
result = False
return result
except Exception as e:
traceback.print_exc(e)

# 查询数量
def get_count(self, sql, param=None):
"""
@summary: 执行查询,返回结果数
@param sql:查询SQL,如果有查询条件,请只指定条件列表,并将条件值使用参数[param]传递进来
@param param: 可选参数,条件列表值(元组/列表)
@return: result list/boolean 查询到的结果集
"""
try:
if param is None:
count = self._cursor.execute(sql)
else:
count = self._cursor.execute(sql, param)
return count
except Exception as e:
traceback.print_exc(e)

# 查询部分
def get_many(self, sql, num, param=None):
"""
@summary: 执行查询,并取出num条结果
@param sql:查询SQL,如果有查询条件,请只指定条件列表,并将条件值使用参数[param]传递进来
@param num:取得的结果条数
@param param: 可选参数,条件列表值(元组/列表)
@return: result list/boolean 查询到的结果集
"""
try:
if param is None:
count = self._cursor.execute(sql)
else:
count = self._cursor.execute(sql, param)
if count > 0:
result = self._cursor.fetchmany(num)
else:
result = False
return result
except Exception as e:
traceback.print_exc(e)

# 插入一条数据
def insert_one(self, sql, value):
"""
@summary: 向数据表插入一条记录
@param sql:要插入的SQL格式
@param value:要插入的记录数据tuple/list
@return: insertId 受影响的行数
"""
try:
row_count = self._cursor.execute(sql, value)
self.end("commit")
return row_count
except Exception as e:
traceback.print_exc(e)
self.end("rollback")

# 插入多条数据
def insert_many(self, sql, values):
"""
@summary: 向数据表插入多条记录
@param sql:要插入的SQL格式
@param values:要插入的记录数据tuple(tuple)/list[list]
@return: count 受影响的行数
"""
try:
row_count = self._cursor.executemany(sql, values)
self.end("commit")
return row_count
except Exception as e:
traceback.print_exc(e)
self.end("rollback")

# def __get_insert_id(self):
# """
# 获取当前连接最后一次插入操作生成的id,如果没有则为0
# """
# self._cursor.execute("SELECT @@IDENTITY AS id")
# result = self._cursor.fetchall()
# return result[0]['id']

# 执行sql
def __query(self, sql, param=None):
try:
if param is None:
count = self._cursor.execute(sql)
else:
count = self._cursor.execute(sql, param)
self.end("commit")
return count
except Exception as e:
traceback.print_exc(e)
self.end("rollback")

# 更新
def update(self, sql, param=None):
"""
@summary: 更新数据表记录
@param sql: SQL格式及条件,使用(%s,%s)
@param param: 要更新的 值 tuple/list
@return: count 受影响的行数
"""
return self.__query(sql, param)

# 删除
def delete(self, sql, param=None):
"""
@summary: 删除数据表记录
@param sql: SQL格式及条件,使用(%s,%s)
@param param: 要删除的条件 值 tuple/list
@return: count 受影响的行数
"""
return self.__query(sql, param)

def begin(self):
"""
@summary: 开启事务
"""
self._conn.autocommit(0)

def end(self, option='commit'):
"""
@summary: 结束事务
"""
if option == 'commit':
self._conn.commit()
else:
self._conn.rollback()

def dispose(self, is_end=1):
"""
@summary: 释放连接池资源
"""
if is_end == 1:
self.end('commit')
else:
self.end('rollback')
self._cursor.close()
self._conn.close()

新建数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CREATE DATABASE mypython

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for fiction
-- ----------------------------
DROP TABLE IF EXISTS `fiction`;
CREATE TABLE `fiction` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
`chapter` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '小说章节数',
`title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '小说标题',
`content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT '小说内容',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

配置数据库连接

在settings.py 中添加下面的数据库连接配置

1
2
3
4
5
6
# MYSQL SETTINGS
MYSQL_HOST = '127.0.0.1'
MYSQL_USER = 'root'
MYSQL_PASSWORD = '123456'
MYSQL_DATABASE = 'mypython'
MYSQL_PORT = 3306

对代码进行编写

根据页面分析,小说页面的末端存在【下一页】,根据爬虫自动触发 不断的循环爬取小说
// 这里爬取了 小说的:章节数、标题、内容
由于 章节数、标题 在同一个div中,所以需要自定义规则进行拆分
先根据章节数,从数据库查询是否存在该小说,如果不存在,就对提取出来的数据进行存库;如果存在,则不做操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import scrapy
# 引入mysql工具类
from ..mysql import MysqlUtil

# 运行scrapy项目的命令: scrapy crawl test
class myclass(scrapy.Spider):
# 爬虫名
name ='test'
# 爬取的链接
start_urls = ["http://book.zongheng.com/chapter/885037/58155562.html"]

def __init__(self):
self.pool = MysqlUtil()

def parse(self, response):

# 标题章节div(包括章节数、本章名称)
title_1 = response.xpath("//div[@class='title_txtbox']/text()").extract()[0]

# 1.提取标题,章节数以外的文字
if "章" in str(title_1):
title = str(title_1)[str(title_1).index("章") + 1:]
else:
title = str(title_1)

# 2.提取章节数,根据"章"字对标题进行截取
if "章" in str(title_1):
chapter = str(title_1)[0:str(title_1).index("章") + 1]
else:
chapter = "-"

# 3.内容 content
content_1 = response.xpath("//div[@class='content']//text()").extract()
# 数组转字符串: String = "".join(arrary)
# 字符串清除空格: String.strip()
# 数组转字符串,并清空内容里的空格
content = "".join(content_1).strip()

# 数据入库
selectSql = """select * from fiction where chapter = %s """
params1 = (chapter)
returnData = self.pool.get_one(selectSql, params1)
if returnData == False:
insertSql = """insert into fiction(chapter, title, content)values(%s, %s, %s)"""
params = (chapter, title, content)
self.pool.insert_one(insertSql, params)

# 下一页
link = response.xpath("//div[@class='chap_btnbox']//a[@class='nextchapter']/@href").extract()[0]
print(link)

# 重复循环爬取
yield scrapy.Request(link, callback=self.parse, dont_filter=True)

启动scrapy爬虫项目

1
2
# scrapy crawl 爬虫名
scrapy crawl test

慢慢等着,然后数据库就开始进数据了~~~