0%

爬取某书网指定小说

初探Python - 用Python实现对小说的爬取并下载

起因

最近看了一部基于小说改编的电视剧,闲的无聊就去看了看小说。但是大多数看书网站都有或多或少的广告,强迫症表示很难受,加之近来正在学习Python,便想着利用Python爬取小说的内容,远离广告的‘宠信’。

实现原理

程序实现原理很简单,将用户输入的书名提交到全书网进行搜索,将返回的结果存在list中并展示给用户,对用户选择的书籍进行书籍内容的爬取,并写入文件。

存在的问题

初次学习写这种爬虫,代码难免冗余难读,但关键部分均写有注释,在此分享出来以作交流。
在实际操作过程中也遇到不少问题,比如:如果所查询的书籍在全书网中只有唯一的一本,全书网将直接通过location进行重定向到书籍详情页,与多本书的处理方式不一致,所以为了完美的获取书记简介必须写个if判断;再如:用户提交的书名必须经过gb2312编码并转urlencoding后才能查询到,否则服务器返回错误。。。不过,也可能是我第一次写,有更好的处理方法,希望自己继续努力吧!

源代码:

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
# -*- coding: utf-8 -*-
import requests,sys,re
import urllib.request

class Bookspider(object):
"""爬取全书网指定书籍并下载到本地"""
def __init__(self):
super(Bookspider, self).__init__()
self.request = requests.session()
self.headers = {
#随机指定user-agent
'User-Agent': 'User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'
}

def book_search(self,name):
print('[*]搜索中...')
#全书网的搜索url
#对name进行url编码:urllib.request.quote
self.url = 'http://www.quanshuwang.com/modules/article/search.php?searchkey='+urllib.request.quote(name.encode('gb2312'))+'&searchtype=articlename&searchbuttom.x=88&searchbuttom.y=22'
#禁止重定向:allow_redirects=False
search_data = self.request.get(self.url,headers=self.headers,allow_redirects=False)
try:
#所爬书籍唯一时被重定向到location
self.location = search_data.headers['location']
#将str的location转换成list,方便计算length
self.location = self.location.split()
except KeyError as e:
response = search_data.content.decode('gbk')
#所爬书籍有多种结果时,返回所有书籍信息
self.location = re.findall(r'<li><a target="_blank" href="(.*?)".*?title="(.*?)".*>(.*?)</a><em class=.*?>(.*?)<a href=',response)
return self.location

def book_info(self):
res = requests.get(self.location,headers=self.headers).content.decode('gbk')
#正则匹配书名
self.bookname = re.findall(r'<meta property="og:title" content="(.*?)"/>',res)[0]
#正则匹配书籍作者
self.author = re.findall(r'<meta property="og:novel:author" content="(.*?)"/>',res)[0]
#正则匹配书记简介 re.S指定多行匹配
self.description = re.findall(r'<meta property="og:description" content="(.*?)"/>',res,re.S)[0].replace('&nbsp;','').replace('<br />','').replace(' ','')
#正则匹配书籍目录
self.booklink = re.findall(r'class="leftso png_bg"><a href="(.*?)"',res)[0]
response = requests.get(self.booklink,headers=self.headers).content.decode('gbk')
#正则匹配所有章节及其链接
self.contents = re.findall(r'<li><a href="(.*?)" title="(.*?)">.*?</a></li>',response)
#总章数
self.len = len(self.contents)

def save(self):
i = 0

#开始写入文件
with open('%s.txt'%self.bookname,'w',encoding='utf-8',errors='ignore') as text_save:
text_save.write(self.bookname+'\n作者:'+self.author)
text_save.write('\n简介:'+self.description)
text_save.write('\n')
for num in self.contents:
#获取单章文本
text_r = requests.get(num[0],headers=self.headers)
text_c = text_r.content
text = text_c.decode('gbk')
#获取章节名
title = num[1].replace(re.findall(r',共\d*字',num[1])[0],'')
try:
#正则匹配内容
text = re.findall(r'</script>&nbsp;&nbsp;&nbsp;&nbsp;(.*?)<script type="text/javascript">',text,re.S)[0]
except IndexError as e:
print('【**】%s下载失败'%title)
#数据清洗
text = text.replace('&nbsp;&nbsp;&nbsp;&nbsp;','')
text = text.replace('<br />','')
text = text.replace('\r\n\r\n','\r\n')

text_save.write('\n')
text_save.write(title)
text_save.write('\n\n')
text_save.write(text)
text_save.write('\n')
print('[*]%-30s\t完成度%.3f%%'%(title,(i+1) * 100 / self.len))
i += 1
text_save.write('\n===全书完===\n==by苏乞儿==\n')

def get_item(self):
item = len(self.location)
return item

def set_location(self,location):
self.location = location

def get_bookname(self):
return self.bookname

def get_author(self):
return self.author

#获取总章数
def get_chapter(self):
return self.len

def get_description(self):
return self.description

if __name__ == '__main__':
book = Bookspider()
name = input('[*]请输入书名:')
#查书
locations = book.book_search(name)
item_sum = book.get_item()
#以下为两种搜索结果的处理,1.所爬的书籍在全书网是唯一的,2.所爬的书籍在全书网有多本
if item_sum == 1:
book.set_location(locations[0])
book.book_info()
print('[*]%s共%d条搜索结果:\n'%(name,item_sum))
print('#'*150)
print('[*]书名:%s'%book.get_bookname())
print('[*]作者:%s'%book.get_author())
print('[*]简介:%s'%book.get_description())
print('#'*150)
choose1 = input('[*]是否继续下载?(y/n)')
if choose1 == 'y' or choose1 == 'Y':
print('\n[*]正在下载《%s》'%book.get_bookname())
#保存文本
book.save()
else:
print('\n[*]感谢使用!')
sys.exit(1)

else:
j = 0
print('[*]%s共%d条搜索结果:\n'%(name,item_sum))
for item in locations:
print('#'*150)
print('[*]序号:%d'%(j+1))
print('[*]书名:%s'%item[1])
print('[*]作者:%s'%item[2])
print('[*]简介:%s'%item[3])
print('#'*150)
j +=1
choose2 = int(input('\n\n请选择序号:'))
if choose2 >= 1 and choose2 <= item_sum:
print('\n[*]正在下载《%s》'%locations[choose2-1][1])
#设置书籍链接地址
book.set_location(locations[choose2-1][0])
book.book_info()
#保存文本
book.save()
else:
print('\n[*]感谢使用!')
sys.exit(1)

print('\n[*]下载成功!')

部分运行截图:

开始

选择

运行中

下载的文本文件:

文本

更新(2018年11月26日)

后期使用过程发现还有不少bug,不过毕竟算第一篇爬虫,这边就不删除了,留个纪念吧!较完善的服务器版本在这里:爬取某书网指定小说-微信版

您的支持将鼓励我的创作!

欢迎关注我的其它发布渠道