学习了爬取天气预报,今天尝试做个爬取小说工具,有时候网上看看小说休闲下,打算保存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 :
- # -*- coding: UTF-8 -*-
- # 爬取静态网页工具
- import requests
- import time
- import re
- from bs4 import BeautifulSoup
- import random
- import json
- import os
-
-
- def get_html_text(url):
- '''
- @方法名称: 获取网页的html信息
- @中文注释: 获取网页的html信息,转换成字符串格式数据
- @入参:
- @param url str 网址
- @出参:
- @返回状态:
- @return 0 失败或异常
- @return 1 成功
- @返回错误码
- @返回错误信息
- @param rsp_text str 网页html信息
- @作 者: PandaCode辉
- @创建时间: 2023-09-05
- @使用范例: get_html_text('https://www.baidu.com/')
- '''
- try:
- if (not type(url) is str):
- return [0, "111111", "网址参数类型错误,不为字符串", [None]]
-
- # 浏览器用户信息列表
- user_agents = [
- 'Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11',
- 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0',
- 'Opera/9.25 (Windows NT 5.1; U; en)',
- 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)',
- 'Mozilla/5.0 (compatible; Konqueror/3.5; Linux) KHTML/3.5.5 (like Gecko) (Kubuntu)',
- 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.12) Gecko/20070731 Ubuntu/dapper-security Firefox/1.5.0.12',
- 'Lynx/2.8.5rel.1 libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/1.2.9',
- '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',
- 'Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:10.0) Gecko/20100101 Firefox/10.0',
- ]
- # 随机获取一个浏览器用户信息
- agent = random.choice(user_agents)
- # header头信息
- headers = {
- 'User-Agent': agent,
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
- 'Accept-Language': 'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
- 'Accept-Encoding': 'gzip, deflate',
- 'Connection': 'keep-alive',
- 'Cache-Control': 'max-age=0',
- }
- # 代理IP地址,需要找到可用的代理ip,不然无法使用
- # proxy = {'HTTP': 'xx.xx.xx.xx:8000', 'HTTPS': 'xx.xx.xx.xx:80'}
- # response = requests.get(url, headers=headers, proxies=proxy, timeout=30)
- # 增加随机模拟浏览器访问header头信息,提高反爬网站成功率
- response = requests.get(url, headers=headers, timeout=30)
- # print(response.status_code)
- response.raise_for_status()
- response.encoding = 'utf-8'
- rsp_text = response.text
- # 返回容器
- return [1, '000000', '获取网页的html信息成功', [rsp_text]]
-
- except Exception as e:
- print("获取网页的html信息异常," + str(e))
- return [0, '999999', "获取网页的html信息异常," + str(e), [None]]
-
-
-
- def spider_novel_mulu(req_dict):
- '''
- @方法名称: 爬取小说章节目录
- @中文注释: 根据参数爬取小说目录,保存到json文件
- @入参:
- @param req_dict dict 请求容器
- @出参:
- @返回状态:
- @return 0 失败或异常
- @return 1 成功
- @返回错误码
- @返回错误信息
- @param rsp_dict dict 响应容器
- @作 者: PandaCode辉
- @创建时间: 2023-09-06
- @使用范例: spider_novel_mulu(req_dict)
- '''
-
- try:
- if (not type(req_dict) is dict):
- return [0, "111111", "请求容器参数类型错误,不为字典", [None]]
- # 目录分页,1-首页,2-下一页
- mulu_page = '1'
- url = req_dict['mulu_url']
- # 章节目录汇总列表
- tar_dir_href_list = []
- i = 1
- del_index = 0
- # 遍历目录分页
- while (True):
- # 根据url地址获取网页信息
- rst = get_html_text(url)
- if rst[0] != 1:
- return rst
- html_str = rst[3][0]
- # 使用BeautifulSoup解析网页数据
- soup = BeautifulSoup(html_str, "html.parser")
-
- # 目录列表地址
- tar_dir_href = soup.select(req_dict['tar_dir_href'])
- # print(tar_dir_href)
- # tar_len = len(tar_dir_href)
- # print('初始爬取章节总数量:', tar_len)
- # 目录分页,1-首页,2-下一页
- if mulu_page == '1':
- for dir_href in tar_dir_href:
- chap_title = dir_href.text
- # print(chap_title)
- if '第1章' in chap_title:
- break
- del_index += 1
- # 过滤章节下标
- print(del_index)
- # 过滤章节,从第一章开始
- tar_dir_href_list = tar_dir_href[del_index:]
- tar_len = len(tar_dir_href_list)
- print('过滤后章节总数量:', tar_len)
- # 目录分页,1-首页,2-下一页
- mulu_page = '2'
- else:
- # 过滤章节,从第一章开始
- tar_dir_href = tar_dir_href[del_index:]
- # 判断一个list是否包含另一个list的全部元素
- is_in_list = [False for tmp in tar_dir_href if tmp not in tar_dir_href_list]
- if is_in_list:
- print('tar_dir_href_list 不包含 tar_dir_href 的所有元素!可以合并添加列表')
- # 合并列表
- tar_dir_href_list.extend(tar_dir_href)
- tar_len = len(tar_dir_href)
- print('过滤后章节总数量:', tar_len)
- else:
- print('tar_dir_href_list 包含 tar_dir_href 的所有元素!跳出循环,结束。')
- break
- # 目录下一页url
- # 'https://www.douyinxs.com/bqg/1081818_10/'
- i += 1
- url = req_dict['mulu_url'][:-1] + '_' + str(i) + '/'
- print('目录下一页url: ' + url)
- tar_len = len(tar_dir_href_list)
- print('合并后章节总数量:', tar_len)
- # 目录容器
- mulu_dict = {}
- # 章节标题,列表
- mulu_dict['chap_title'] = []
- # 章节url,列表
- mulu_dict['chap_url'] = []
- # 是否完成标志: 0-未完成,1-已完成,列表
- mulu_dict['flag'] = []
- # 循环读取章节
- for dir_href in tar_dir_href_list:
- # 章节标题
- chap_title = dir_href.text
- print(chap_title)
- mulu_dict['chap_title'].append(chap_title)
- # 章节url
- chap_url = req_dict['novel_url'] + dir_href['href']
- print(chap_url)
- mulu_dict['chap_url'].append(chap_url)
- mulu_dict['flag'].append('0')
- # 转换为json字符串
- json_str = json.dumps(mulu_dict)
- json_name = req_dict['novel_name'] + '.json'
- # 写入json文件
- with open(json_name, 'w', encoding="utf-8") as json_file:
- json_file.write(json_str)
- # 返回容器
- return [1, '000000', '爬取小说目录成功', [None]]
-
- except Exception as e:
- print("爬取小说目录异常," + str(e))
- return [0, '999999', "爬取小说目录异常," + str(e), [None]]
-
-
- def spider_novel_content(req_dict):
- '''
- @方法名称: 爬取小说章节明细内容
- @中文注释: 读取章节列表json文件,爬取小说章节明细内容,保存到文本文件
- @入参:
- @param req_dict dict 请求容器
- @出参:
- @返回状态:
- @return 0 失败或异常
- @return 1 成功
- @返回错误码
- @返回错误信息
- @param rsp_dict dict 响应容器
- @作 者: PandaCode辉
- @创建时间: 2023-09-06
- @使用范例: spider_novel_content(req_dict)
- '''
-
- try:
- if (not type(req_dict) is dict):
- return [0, "111111", "请求容器参数类型错误,不为字典", [None]]
- # 章节目录文件名
- json_name = req_dict['novel_name'] + '.json'
- # 检查文件是否存在
- if os.path.isfile(json_name):
- print('json文件存在,不用重新爬取小说目录.')
- else:
- print('json文件不存在')
- # 爬取小说目录
- spider_novel_mulu(req_dict)
- # 读取json文件
- with open(json_name, 'r') as f:
- data_str = f.read()
- # 转换为字典容器
- mulu_dict = json.loads(data_str)
- """
- 关于open()的mode参数:
- 'r':读
- 'w':写
- 'a':追加
- 'r+' == r+w(可读可写,文件若不存在就报错(IOError))
- 'w+' == w+r(可读可写,文件若不存在就创建)
- 'a+' ==a+r(可追加可写,文件若不存在就创建)
- 对应的,如果是二进制文件,就都加一个b就好啦:
- 'rb' 'wb' 'ab' 'rb+' 'wb+' 'ab+'
- """
- file_name = req_dict['novel_name'] + '.txt'
- # 在列表中查找指定元素的下标,未完成标志下标
- flag_index = mulu_dict['flag'].index('0')
- print(flag_index)
- # 未完成标志下标为0,则为第一次爬取章节内容,否则已经写入部分,只能追加内容写入文件
- # 因为章节明细内容很多,防止爬取过程中间中断,重新爬取,不用重复再爬取之前成功写入的数据
- if flag_index == 0:
- # 打开文件,首次创建写入
- fo = open(file_name, "w+", encoding="utf-8")
- else:
- # 打开文件,再次追加写入
- fo = open(file_name, "a+", encoding="utf-8")
- # 章节总数
- chap_len = len(mulu_dict['chap_url'])
- # 在列表中查找指定元素的下标
- print('章节总数:', chap_len)
- out_flag = False
- # 循环读取章节
- for i in range(flag_index, chap_len):
- # 章节标题
- chap_title = mulu_dict['chap_title'][i]
- print(chap_title)
- # 写入文件,章节标题
- fo.write(chap_title + "\r\n")
- # 章节url
- chap_url = mulu_dict['chap_url'][i]
- # 章节内容分页数
- for j in range(2):
- if j > 0:
- # 章节内容下一页url
- chap_url = mulu_dict['chap_url'][i][:-5] + '_' + str(j + 1) + '.html'
- print(chap_url)
- # 再爬取明细章节内容
- # 根据url地址获取网页信息
- chap_rst = get_html_text(chap_url)
- if chap_rst[0] != 1:
- # 跳出循环爬取
- out_flag = True
- break
- chap_html_str = chap_rst[3][0]
- # 使用BeautifulSoup解析网页数据
- chap_soup = BeautifulSoup(chap_html_str, "html.parser")
- # 章节内容
- chap_content = chap_soup.select(req_dict['chap_content'])[0].text
- chap_content = chap_content.replace(' ', '\n').replace(' ', '\n')
- # print(chap_content)
- # 写入文件,章节内容
- fo.write(chap_content + "\r\n")
- # 跳出循环爬取标志
- if out_flag:
- print("跳出循环爬取标志")
- break
- # 爬取明细章节内容成功后,更新对应标志为-1-已完成
- mulu_dict['flag'][i] = '1'
- # 关闭文件
- fo.close()
- print("循环爬取明细章节内容,写入文件完成")
- # 转换为json字符串
- json_str = json.dumps(mulu_dict)
- # 再次写入json文件,保存更新处理完标志
- with open(json_name, 'w', encoding="utf-8") as json_file:
- json_file.write(json_str)
- print("再次写入json文件,保存更新处理完标志")
- # 返回容器
- return [1, '000000', '爬取小说内容成功', [None]]
-
- except Exception as e:
- # 转换为json字符串
- json_str = json.dumps(mulu_dict)
- # 再次写入json文件,保存更新处理完标志
- with open(json_name, 'w', encoding="utf-8") as json_file:
- json_file.write(json_str)
- print("再次写入json文件,保存更新处理完标志")
- print("爬取小说内容异常," + str(e))
- return [0, '999999', "爬取小说内容异常," + str(e), [None]]
-
-
- # 主方法
- if __name__ == '__main__':
-
-
- req_dict = {}
- req_dict['novel_name'] = '重生八八从木匠开始'
- req_dict['novel_url'] = 'https://www.douyinxs.com'
- # 目录页面地址,第1页
- req_dict['mulu_url'] = 'https://www.douyinxs.com/bqg/1081818/'
- # 章节目录定位
- req_dict['tar_dir_href'] = "div#list > dl > dd > a"
- # 章节明细内容定位
- req_dict['chap_content'] = 'article#content'
- # 爬取小说目录
- # spider_novel_mulu(req_dict)
- # 爬取小说内容
- spider_novel_content(req_dict)
