• bs4库爬取小说工具


    学习了爬取天气预报,今天尝试做个爬取小说工具,有时候网上看看小说休闲下,打算保存txt文本文件,方便离线阅读。

    第一步:先确定目标网址

    网上随便找了本小说,先找到小说目录页面。

    网址首页:'https://www.douyinxs.com'

    目标小说目录页:'https://www.douyinxs.com/bqg/1081818/'

    第二步:再定位章节目录url

    按F12,确定章节位置。

     章节目录url定位: "div#list > dl > dd > a"

    第三步:再定位章节明细内容

    按F12,确定章节内容位置。

    第四步:编写代码

    采用2步爬取策略:

    1,先爬取小说章节目录url和标题信息保存json文件。

    2,再读取章节目录json文件,循环一条条爬取章节明细内容信息,写入txt文件中。

    3,章节明细数量太多,很容易出现异常情况,增加了异常重发处理机制。

    requests_bs4.py :

    1. # -*- coding: UTF-8 -*-
    2. # 爬取静态网页工具
    3. import requests
    4. import time
    5. import re
    6. from bs4 import BeautifulSoup
    7. import random
    8. import json
    9. import os
    10. def get_html_text(url):
    11. '''
    12. @方法名称: 获取网页的html信息
    13. @中文注释: 获取网页的html信息,转换成字符串格式数据
    14. @入参:
    15. @param url str 网址
    16. @出参:
    17. @返回状态:
    18. @return 0 失败或异常
    19. @return 1 成功
    20. @返回错误码
    21. @返回错误信息
    22. @param rsp_text str 网页html信息
    23. @作 者: PandaCode辉
    24. @创建时间: 2023-09-05
    25. @使用范例: get_html_text('https://www.baidu.com/')
    26. '''
    27. try:
    28. if (not type(url) is str):
    29. return [0, "111111", "网址参数类型错误,不为字符串", [None]]
    30. # 浏览器用户信息列表
    31. user_agents = [
    32. 'Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11',
    33. 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0',
    34. 'Opera/9.25 (Windows NT 5.1; U; en)',
    35. 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)',
    36. 'Mozilla/5.0 (compatible; Konqueror/3.5; Linux) KHTML/3.5.5 (like Gecko) (Kubuntu)',
    37. 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.12) Gecko/20070731 Ubuntu/dapper-security Firefox/1.5.0.12',
    38. 'Lynx/2.8.5rel.1 libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/1.2.9',
    39. 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.7 (KHTML, like Gecko) Ubuntu/11.04 Chromium/16.0.912.77 Chrome/16.0.912.77 Safari/535.7',
    40. 'Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:10.0) Gecko/20100101 Firefox/10.0',
    41. ]
    42. # 随机获取一个浏览器用户信息
    43. agent = random.choice(user_agents)
    44. # header头信息
    45. headers = {
    46. 'User-Agent': agent,
    47. 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    48. 'Accept-Language': 'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
    49. 'Accept-Encoding': 'gzip, deflate',
    50. 'Connection': 'keep-alive',
    51. 'Cache-Control': 'max-age=0',
    52. }
    53. # 代理IP地址,需要找到可用的代理ip,不然无法使用
    54. # proxy = {'HTTP': 'xx.xx.xx.xx:8000', 'HTTPS': 'xx.xx.xx.xx:80'}
    55. # response = requests.get(url, headers=headers, proxies=proxy, timeout=30)
    56. # 增加随机模拟浏览器访问header头信息,提高反爬网站成功率
    57. response = requests.get(url, headers=headers, timeout=30)
    58. # print(response.status_code)
    59. response.raise_for_status()
    60. response.encoding = 'utf-8'
    61. rsp_text = response.text
    62. # 返回容器
    63. return [1, '000000', '获取网页的html信息成功', [rsp_text]]
    64. except Exception as e:
    65. print("获取网页的html信息异常," + str(e))
    66. return [0, '999999', "获取网页的html信息异常," + str(e), [None]]
    67. def spider_novel_mulu(req_dict):
    68. '''
    69. @方法名称: 爬取小说章节目录
    70. @中文注释: 根据参数爬取小说目录,保存到json文件
    71. @入参:
    72. @param req_dict dict 请求容器
    73. @出参:
    74. @返回状态:
    75. @return 0 失败或异常
    76. @return 1 成功
    77. @返回错误码
    78. @返回错误信息
    79. @param rsp_dict dict 响应容器
    80. @作 者: PandaCode辉
    81. @创建时间: 2023-09-06
    82. @使用范例: spider_novel_mulu(req_dict)
    83. '''
    84. try:
    85. if (not type(req_dict) is dict):
    86. return [0, "111111", "请求容器参数类型错误,不为字典", [None]]
    87. # 目录分页,1-首页,2-下一页
    88. mulu_page = '1'
    89. url = req_dict['mulu_url']
    90. # 章节目录汇总列表
    91. tar_dir_href_list = []
    92. i = 1
    93. del_index = 0
    94. # 遍历目录分页
    95. while (True):
    96. # 根据url地址获取网页信息
    97. rst = get_html_text(url)
    98. if rst[0] != 1:
    99. return rst
    100. html_str = rst[3][0]
    101. # 使用BeautifulSoup解析网页数据
    102. soup = BeautifulSoup(html_str, "html.parser")
    103. # 目录列表地址
    104. tar_dir_href = soup.select(req_dict['tar_dir_href'])
    105. # print(tar_dir_href)
    106. # tar_len = len(tar_dir_href)
    107. # print('初始爬取章节总数量:', tar_len)
    108. # 目录分页,1-首页,2-下一页
    109. if mulu_page == '1':
    110. for dir_href in tar_dir_href:
    111. chap_title = dir_href.text
    112. # print(chap_title)
    113. if '第1章' in chap_title:
    114. break
    115. del_index += 1
    116. # 过滤章节下标
    117. print(del_index)
    118. # 过滤章节,从第一章开始
    119. tar_dir_href_list = tar_dir_href[del_index:]
    120. tar_len = len(tar_dir_href_list)
    121. print('过滤后章节总数量:', tar_len)
    122. # 目录分页,1-首页,2-下一页
    123. mulu_page = '2'
    124. else:
    125. # 过滤章节,从第一章开始
    126. tar_dir_href = tar_dir_href[del_index:]
    127. # 判断一个list是否包含另一个list的全部元素
    128. is_in_list = [False for tmp in tar_dir_href if tmp not in tar_dir_href_list]
    129. if is_in_list:
    130. print('tar_dir_href_list 不包含 tar_dir_href 的所有元素!可以合并添加列表')
    131. # 合并列表
    132. tar_dir_href_list.extend(tar_dir_href)
    133. tar_len = len(tar_dir_href)
    134. print('过滤后章节总数量:', tar_len)
    135. else:
    136. print('tar_dir_href_list 包含 tar_dir_href 的所有元素!跳出循环,结束。')
    137. break
    138. # 目录下一页url
    139. # 'https://www.douyinxs.com/bqg/1081818_10/'
    140. i += 1
    141. url = req_dict['mulu_url'][:-1] + '_' + str(i) + '/'
    142. print('目录下一页url: ' + url)
    143. tar_len = len(tar_dir_href_list)
    144. print('合并后章节总数量:', tar_len)
    145. # 目录容器
    146. mulu_dict = {}
    147. # 章节标题,列表
    148. mulu_dict['chap_title'] = []
    149. # 章节url,列表
    150. mulu_dict['chap_url'] = []
    151. # 是否完成标志: 0-未完成,1-已完成,列表
    152. mulu_dict['flag'] = []
    153. # 循环读取章节
    154. for dir_href in tar_dir_href_list:
    155. # 章节标题
    156. chap_title = dir_href.text
    157. print(chap_title)
    158. mulu_dict['chap_title'].append(chap_title)
    159. # 章节url
    160. chap_url = req_dict['novel_url'] + dir_href['href']
    161. print(chap_url)
    162. mulu_dict['chap_url'].append(chap_url)
    163. mulu_dict['flag'].append('0')
    164. # 转换为json字符串
    165. json_str = json.dumps(mulu_dict)
    166. json_name = req_dict['novel_name'] + '.json'
    167. # 写入json文件
    168. with open(json_name, 'w', encoding="utf-8") as json_file:
    169. json_file.write(json_str)
    170. # 返回容器
    171. return [1, '000000', '爬取小说目录成功', [None]]
    172. except Exception as e:
    173. print("爬取小说目录异常," + str(e))
    174. return [0, '999999', "爬取小说目录异常," + str(e), [None]]
    175. def spider_novel_content(req_dict):
    176. '''
    177. @方法名称: 爬取小说章节明细内容
    178. @中文注释: 读取章节列表json文件,爬取小说章节明细内容,保存到文本文件
    179. @入参:
    180. @param req_dict dict 请求容器
    181. @出参:
    182. @返回状态:
    183. @return 0 失败或异常
    184. @return 1 成功
    185. @返回错误码
    186. @返回错误信息
    187. @param rsp_dict dict 响应容器
    188. @作 者: PandaCode辉
    189. @创建时间: 2023-09-06
    190. @使用范例: spider_novel_content(req_dict)
    191. '''
    192. try:
    193. if (not type(req_dict) is dict):
    194. return [0, "111111", "请求容器参数类型错误,不为字典", [None]]
    195. # 章节目录文件名
    196. json_name = req_dict['novel_name'] + '.json'
    197. # 检查文件是否存在
    198. if os.path.isfile(json_name):
    199. print('json文件存在,不用重新爬取小说目录.')
    200. else:
    201. print('json文件不存在')
    202. # 爬取小说目录
    203. spider_novel_mulu(req_dict)
    204. # 读取json文件
    205. with open(json_name, 'r') as f:
    206. data_str = f.read()
    207. # 转换为字典容器
    208. mulu_dict = json.loads(data_str)
    209. """
    210. 关于open()的mode参数:
    211. 'r':读
    212. 'w':写
    213. 'a':追加
    214. 'r+' == r+w(可读可写,文件若不存在就报错(IOError))
    215. 'w+' == w+r(可读可写,文件若不存在就创建)
    216. 'a+' ==a+r(可追加可写,文件若不存在就创建)
    217. 对应的,如果是二进制文件,就都加一个b就好啦:
    218. 'rb'  'wb'  'ab'  'rb+'  'wb+'  'ab+'
    219. """
    220. file_name = req_dict['novel_name'] + '.txt'
    221. # 在列表中查找指定元素的下标,未完成标志下标
    222. flag_index = mulu_dict['flag'].index('0')
    223. print(flag_index)
    224. # 未完成标志下标为0,则为第一次爬取章节内容,否则已经写入部分,只能追加内容写入文件
    225. # 因为章节明细内容很多,防止爬取过程中间中断,重新爬取,不用重复再爬取之前成功写入的数据
    226. if flag_index == 0:
    227. # 打开文件,首次创建写入
    228. fo = open(file_name, "w+", encoding="utf-8")
    229. else:
    230. # 打开文件,再次追加写入
    231. fo = open(file_name, "a+", encoding="utf-8")
    232. # 章节总数
    233. chap_len = len(mulu_dict['chap_url'])
    234. # 在列表中查找指定元素的下标
    235. print('章节总数:', chap_len)
    236. out_flag = False
    237. # 循环读取章节
    238. for i in range(flag_index, chap_len):
    239. # 章节标题
    240. chap_title = mulu_dict['chap_title'][i]
    241. print(chap_title)
    242. # 写入文件,章节标题
    243. fo.write(chap_title + "\r\n")
    244. # 章节url
    245. chap_url = mulu_dict['chap_url'][i]
    246. # 章节内容分页数
    247. for j in range(2):
    248. if j > 0:
    249. # 章节内容下一页url
    250. chap_url = mulu_dict['chap_url'][i][:-5] + '_' + str(j + 1) + '.html'
    251. print(chap_url)
    252. # 再爬取明细章节内容
    253. # 根据url地址获取网页信息
    254. chap_rst = get_html_text(chap_url)
    255. if chap_rst[0] != 1:
    256. # 跳出循环爬取
    257. out_flag = True
    258. break
    259. chap_html_str = chap_rst[3][0]
    260. # 使用BeautifulSoup解析网页数据
    261. chap_soup = BeautifulSoup(chap_html_str, "html.parser")
    262. # 章节内容
    263. chap_content = chap_soup.select(req_dict['chap_content'])[0].text
    264. chap_content = chap_content.replace(' ', '\n').replace(' ', '\n')
    265. # print(chap_content)
    266. # 写入文件,章节内容
    267. fo.write(chap_content + "\r\n")
    268. # 跳出循环爬取标志
    269. if out_flag:
    270. print("跳出循环爬取标志")
    271. break
    272. # 爬取明细章节内容成功后,更新对应标志为-1-已完成
    273. mulu_dict['flag'][i] = '1'
    274. # 关闭文件
    275. fo.close()
    276. print("循环爬取明细章节内容,写入文件完成")
    277. # 转换为json字符串
    278. json_str = json.dumps(mulu_dict)
    279. # 再次写入json文件,保存更新处理完标志
    280. with open(json_name, 'w', encoding="utf-8") as json_file:
    281. json_file.write(json_str)
    282. print("再次写入json文件,保存更新处理完标志")
    283. # 返回容器
    284. return [1, '000000', '爬取小说内容成功', [None]]
    285. except Exception as e:
    286. # 转换为json字符串
    287. json_str = json.dumps(mulu_dict)
    288. # 再次写入json文件,保存更新处理完标志
    289. with open(json_name, 'w', encoding="utf-8") as json_file:
    290. json_file.write(json_str)
    291. print("再次写入json文件,保存更新处理完标志")
    292. print("爬取小说内容异常," + str(e))
    293. return [0, '999999', "爬取小说内容异常," + str(e), [None]]
    294. # 主方法
    295. if __name__ == '__main__':
    296. req_dict = {}
    297. req_dict['novel_name'] = '重生八八从木匠开始'
    298. req_dict['novel_url'] = 'https://www.douyinxs.com'
    299. # 目录页面地址,第1
    300. req_dict['mulu_url'] = 'https://www.douyinxs.com/bqg/1081818/'
    301. # 章节目录定位
    302. req_dict['tar_dir_href'] = "div#list > dl > dd > a"
    303. # 章节明细内容定位
    304. req_dict['chap_content'] = 'article#content'
    305. # 爬取小说目录
    306. # spider_novel_mulu(req_dict)
    307. # 爬取小说内容
    308. spider_novel_content(req_dict)

  • 相关阅读:
    初级项目经理 如何快速提升能力?
    TCP详解之三次握手和四次挥手
    Android studio将一个项目作为module导入另一个项目
    WebDAV之π-Disk派盘 + PassStore
    Spring Boot 整合 Camunda 实现工作流
    如何设置 Jenkins 流水线环境变量
    字符串处理
    安装gstreamer开发依赖库到项目sysroot目录
    Webservice接口-WSDL文档【Webservice】
    详解编译和链接!
  • 原文地址:https://blog.csdn.net/xionghui2007/article/details/132754900