关键组件解释
- 导入库: 使用
aiohttp进行异步HTTP请求,aiofiles异步文件操作,lxml解析HTML,asyncio管理异步任务。 - 准备工作: 定义了目标网站的URL、请求头、字符编码,并检查或创建用于存储下载内容的目录。
- 文章列表函数(
list_html): 请求小说目录页面,解析HTML以获取小说标题和章节列表。章节URL是通过解析得到的相对链接构造的。 - 章节内容函数(
res_content): 解析单个章节的页面,获取章节标题和内容。 - 异步文本下载(
asytxt): 使用aiohttp执行异步GET请求,获取页面内容。 - 处理单章节下载(
asy_html): 异步请求单个章节的内容,如果下载成功,则保存到指定文件中;如果失败,尝试重新下载或记录下来。 - 错误处理和重试(
mistake_txt): 在所有章节下载完成后,检查并重新下载之前失败的章节。 - 下载并保存内容(
downtxt): 使用aiofiles异步写入章节内容到文件中。 - 合并小说章节(
combine_txt): 将所有下载好的章节文件合并成一个完整的小说文件,并清理临时文件。 - 主函数(
main): 调用上述函数,使用asyncio运行异步任务,完成整个下载和合并过程。
工作流程
- 用户运行脚本,脚本首先获取小说的章节列表和标题。
- 通过异步协程并发请求每个章节的内容,大幅提高下载效率。
- 每个章节内容被单独保存到临时文件中,如果下载失败,则记录下来以便后续重试。
- 下载完成后,脚本尝试重新下载之前失败的章节。
- 最后,所有章节被合并成一个完整的小说文件,临时文件被清理。
直接附上源码:
'''
异步协程下载小说
# ---------------------------------------------------------------------------------
# 支持站点:
# 八一中文网(81zw.com)
# 顶点小说(23usp.com)
#笔趣阁(bqg.org,qbiqu.com,52bqg.net等全部站点)
#天籁小说(xs.23sk.com)
# --------------------------------------------------------------------------------
'''
# 2.导入需要用的库文件
import requests,os,time
import asyncio
import aiohttp,aiofiles
from lxml import etree
# 1.准备网站,headers
URL = "https://www.81zw.com/book/73391/"
gbk = 'utf-8'
headers={
'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:82.0) Gecko/20100101 Firefox/82.0'
}
if not os.path.exists('./缓存'): #创建缓存文件夹来存放抓取的每一章节文件
os.mkdir('./缓存')
# 一个返回网页文章列表的函数
def resp_html(url):
html = requests.get(url,headers=headers)
html.encoding = gbk
return html.text
def list_html(url):
html = resp_html(url)
tree=etree.HTML(html)
zjlist=tree.xpath('//dd//a/@href') #获取每一章的页面相对地址
title=tree.xpath('//h1/text()')[0] #获取小说名字
zj_List = []
for u in zjlist: # 组合链接地址
zjname=u.split('/')[-1].split('.')[0]
zj_List.append(int(zjname))
zj_List.sort() #章节排序
return title,zj_List
# 多次调用函数
def res_content(html):
tree=etree.HTML(html)
title=tree.xpath('//h1/text()')[0] #获取每一章名字
txtt=tree.xpath('//div[@id="content"]/text()') #获取每一章文本内容
txt = ""
for line in txtt: #保存章节内容到文本文件,循环保存每一行
txt = txt+"\n"+line
# xs = URL.split('/')[-2] 这个在 “23usp.com” 这个网站有用 .replace("kvpsd https://www.23usp.com/"+xs+"/ 天才一秒记住","")
#替换掉不用的页面内容
txt = txt.replace("https://www.81zw.com","").replace("网页版章节内容慢,请下载爱阅小说app阅读最新内容","").replace("网站即将关闭,下载爱阅app免费看最新内容","").replace("免费阅读。","")
return title,txt
async def asytxt(url):
async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(limit=20,ssl=False)) as session :
async with session.get(url,headers=headers) as resp:
resp.encoding = gbk
html =await resp.text()
code = resp.status
return html,code
# 写一个异步返回text
async def asy_html(nums):
if os.path.exists('./缓存/{}.txt'.format(nums)): #这里是查重,如果有重复文件就不继续采集了
print(nums,"------ 已采集,执行下一个")
else:
url = URL+str(nums)+".html" # 组合网址
html,code = await asytxt(url)
if code == 200:
title,txt = res_content(html)
await downtxt(nums,title,txt)
else:
await asyncio.sleep(1) # 停顿3秒再执行一次
html,code = await asytxt(url)
if code == 200:
title,txt = res_content(html)
await downtxt(nums,title,txt)
else: # 采集不成功,就记录下来
with open('mistake.txt','a',encoding='utf-8') as f:
f.write(str(nums)+'\n')
print(url+' '*10+'-----已记录------第二次下载失败!')
#采集错误的记录在这个文件里
# 这个函数把错误网址在合并文件前再重新采集一遍
def mistake_txt():
if not os.path.exists('./mistake.txt'): #检测错误存储文件,要不没有错误时会报错
print("------漂亮------完美------\n -----你的程序没有出错!-----")
else:
with open('mistake.txt','r+',encoding='utf-8') as f:
ff = f.readlines()
ii = len(ff)
print("共{}条数据。".format(ii))
print("-"*30)
if ii>0:
i = 1
for line in ff:
print("下面采集第 {} 条数据。".format(i))
lines = URL+str(line)+".html"
text = resp_html(lines)
title,txt = res_content(text)
with open('./缓存/{}.txt'.format(line),'w',encoding='UTF-8') as f:
f.write('\n'+title+'\n\n'+txt) #保存章节名字到文本文件
print(title+' '*10+'下载成功')
time.sleep(1)
print("错误网址已经采集完毕!")
else:
print("你没有、没有出错网址!")
print("*"*30)
# 写一个下载文章的函数
async def downtxt(nums,file_name,txt):
async with aiofiles.open('./缓存/{}.txt'.format(nums),'w',encoding='UTF-8') as f:
await f.write(file_name+"\n\n"+txt+"\n\n")
print(file_name +"-"*20+" 已下载完成")
# 写一个合并小说的函数
def combine_txt(title,nums): #合并所有章节文件函数
with open('./小说/{}.txt'.format(title),'a',encoding='utf-8') as f:
for txt in nums: #循环打开缓存中每一章的内容保存到新的文件中
path='./缓存/{}.txt'.format(txt) #设置存放路径
content=open(path,'r',encoding='utf-8').read() #打开每章节文件
f.write(content)
os.remove(path) # 删除缓存的txt文件,调试时可以注释掉
print("已保存 <<"+title+">> 的所有章节!请开心阅读!")
# 写一个主函数
def main():
title,zjlist= list_html(URL) # 返回小说名称和文章列表
try:
F = False
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
tasks = [asy_html(nums) for nums in zjlist]
loop.run_until_complete(asyncio.wait(tasks)) # 激活协程
loop.close()
print("*"*30+"")
#这里检查掉下的错误网址
mistake_txt()
except Exception as e :
F = True
print(e)
print("出现错误时就不执行合并")
if F == False :
# 这里合并文件
combine_txt(title,zjlist)
else:
print("未采集完毕,没有执行查重和合并,请重新运行一次!")
if __name__ == "__main__" :
start = time.time()
main()
end = time.time()
print(end - start, 's')
本文介绍了一个使用Python编写的脚本,利用异步库如aiohttp和aiofiles实现对特定小说网站的章节内容进行并发下载,包括获取目录、解析HTML、异步请求、错误重试和合并章节,以提高下载效率并确保文件完整性。
71万+

被折叠的 条评论
为什么被折叠?



