学习了爬取天气预报,今天尝试做个爬取小说工具,有时候网上看看小说休闲下,打算保存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)