Python异步爬取梁羽生小说网
爬取这个网站的小说,用异步的方式,输出txt文件,小说章节按顺序
写这个的目的是,我在百度搜的的小说爬虫,异步,或多线程,都是没有按照顺序输出,小说阅读体验极差
也为了让自己进一步理解异步
就是爬这个网站 其实爬那个都差不多 http://www.liangyusheng.net/
一般先放代码,然后在写一些,自己的理解
import os
import re
import time
import random
import asyncio
import aiohttp
from lxml import etree
"""
@Date : 2021-4-13
@Author : 江户川_柯北
@Version: v1.0
"""
class GetNovel(object):
"""
这个类爬取www.liangyusheng.net网站的小说
"""
def __init__(self,url):
"""
self.url : 是小说首页的url
"""
#记录开始时间
self.start_time = time.time()
#创建小说保存目录,并切换到此处
try:
self.path = './novel/'
os.mkdir(self.path)
os.chdir(self.path)
except:
os.chdir(self.path)
print("创建文件夹失败,当前工作路径为{}".format(os.getcwd()))
self.url = url
self.headers = [
"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0",
"Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.71 Safari/537.1 LBBROWSER",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E; LBBROWSER)",
"Opera/8.0 (Windows NT 5.1; U; en)",
"Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.50",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 9.50",
]
#既然想用递归就要想办法设置递归深度,但现有技术不会设置全局递归深度,就用n来设置
async def get_url_html(self,n=0):
"""
@return : text 返回小说首页的html
"""
self.session = aiohttp.ClientSession()
n += 1
headers = {
"User-Agent" : random.choice(self.headers),
"Accept-Language" : "zh-CN,zh;q=0.9",
"Accept-Encoding" : "gzip, deflate",
}
# print(headers)
if n == 3:
return ''
else:
async with self.session.get(self.url,headers = headers) as resp:
if resp.status == 200:
print("首页请求成功")
return await resp.read()
else:
return self.get_url_html(n)
async def get_url_list_author(self):
"""
@return: url_list[11:] 返回所有章节url数据
self.name_author初始化,小说名称,以及小说作者
"""
try:
t = await self.get_url_html()
try :t = t.decode('utf-8')
except:pass#能执行到这里说明上面发生错误,也就是,他不是二进制的数据,那他就是正常文本,不需要操作
screen = etree.HTML(t)
url_list = screen.xpath('/html/body/div[@id="wrapper"]/div[@id="main"]/div[@class="box_con"]/div/dl/dd/a/@href')
# print(url_list)
t = t.replace(' ','')
novel_author = re.findall('<h1>(.*?)</h1>[\s\S]+<p>(.*?)<a href="/author/\d+/">(.*?)</a></p>',t)[0]
# print(novel_author)
#这里是小说名称和作者名称
self.name_author = novel_author
return url_list[11:]
except:
self.name_author = ()
return []
def data_clear(self,text):
"""
@return txt 返回小说章节标题+小说正文
"""
try :text = text.decode('utf-8')
except:pass
text = text.replace(' ',' ')
#编译正则表达式
cmp = re.compile('<h1>(.*?)</h1>[\s\S]+<div id="content" deep="3">[\s\S]+</p>([\s\S]+)<div align="center">')
data = re.findall(cmp,text)[0]
#data[0]是章节 data[1]是正文
txt = data[0] + data[1]
txt = txt.replace('<br />','\n').replace('\n\n','\n').replace('\n\n','\n')
txt += '\n\n'
return txt
async def get_text(self,url,num):
"""
@return 字典数据,包含url,小说html,小说章节,小说排序号
"""
headers = {
"User-Agent":random.choice(self.headers),
"Referer":self.url
}
async with self.async_num:
try :
await asyncio.sleep(1)
async with self.session.get(url,headers = headers) as res:
if res.status == 200:
text = await res.read()
print("第{:0>5d}章,请求成功".format(num))
return {url:
{
"text":self.data_clear(text),
"chapter":"第{:0>5d}章".format(num),
"num" : num,
}
}
else:
self.get_text(url,num)
except aiohttp.ClientError as e:
print(f"e:>>>{e}")
self.get_text(url,num)
def write_file(self,content):
"""
写入文件
@return None
"""
with open("{}_{}{}.txt".format(self.name_author[0],self.name_author[1],self.name_author[2]),'a+') as f:
f.write(content)
async def task(self):
"""
@return None
设置任务
"""
#设置并发数量,这里设置了10
self.async_num = asyncio.Semaphore(10)
url_list = await self.get_url_list_author()
url_root = self.url[0:self.url.index('/',7)]
#任务列表
task1 = [asyncio.ensure_future(self.get_text(url_root+url_list[i],i)) for i in range(1,len(url_list))]
resu = await asyncio.gather(*task1)#返回的数据,顺序不对,下面要排序
dic_content = {}#上回老师说字典比列表快
for i in resu:
dic_content.update(i)
'''{url1:
{
"text":'',
"chapter":"第{:0>5d}章".format(num),
"num":num,
},
url2:
{
"text":'',
"chapter":"第{:0>5d}章".format(num),
"num":num,
},
}
'''
await self.session.close()#一定记得关闭会话
#接下来就是写文件,写文件之前要排序,不会异步写文件只能顺序写文件,那么就要排好序
dic_content = dict(sorted(dic_content.items(),key = lambda item:item[1]['num']))
for _,v in dic_content.items():
self.write_file(v['text'])
print("结束!!!---用时{:.2f}".format(time.time()-self.start_time))
def main(self):
loop = asyncio.get_event_loop()
loop.run_until_complete(self.task())
if __name__ == "__main__":
print("只能爬取www.liangyusheng.net网站的小说")
url = input("请输入目标小说首页url地址:").strip()
if "www.liangyusheng.net" not in url:
print("url不对")
exit()
GetNovel(url).main()
这个代码注释有点多哈。。
一点一点来看
1.在__init__()函数里面设置了开始时间,然后设置了工作目录,os.mkdir 是创建文件夹,os.chdir是切换工作目录。这里的工作目录就是最后小说保存位置。self.url就是小说首页的url,下面那个是U-A列表。
2.get_url_html()函数里面的n是处理一些异常的,我想着,他在这里假如没有请求到网页,就会返回异常,但是我想让他重复请求3次,若是,还请求不到就退出,就设置了n=0的默认值,然后response==200就正常返回,要是他不是200就重发请求,n+1,里面的self.session是创建一个异步请求的会话,这里就弄好,可以在后面调用,接下来就是async with 用with是因为方便,要不然还得手动关闭会话,然后就有了响应,resp.status 返回请求的状态码不是200就出现异常,然后resp.read()返回的是二进制数据,这里一会会处理编码。
3.get_url_list_author()这个函数,就是返回首页里面所有章节的连接,通过字符串拼接形成url可以获得文章正文,然后就是用try来处理二进制的编码,用utf-8。然后就用xpath得到url列表,用re得到小说名称和作者名称。(这里用正则是因为想练习一下正则),然后返回第12个以后的url,前12个url都是他最新章节的url,后面才从第一章开始,到最后。
4.data_clear()函数就是处理html得到标题和正文的函数了,这里面也用了re,用replace替换了一些垃圾字符,这些太影响阅读了,这里需要注意re.compile()这个是编译正则表达式(据网上传言说更快,这我也看不出来)。。。
5.get_text()这个函数,就是异步的到小说章节的html,也是异步,这里最最关键的是async with self.async_num 这个设置了异步的数量,以前在网上没找到,昨天偶然看见的记下了(不用这个并发太多,不给你爬)这个我设置在task()函数里面了一会讲。接下来就是停1秒,然后发送请求,等待所有响应结果,然后return 一个字典类型的数据,text字段是处理后的数据,num是他的排序号,有这个后面才能顺序输出,chaper字段没啥用,忘记删了。不管他。。。
6.write_file()这个没啥好说的,就是打开文件写入
7.task()这个函数就大有说道了,全局之关键。self.async_num设置了并发数量,asyncio.Semaphore(10)设置并发数为10。然后await等待url_list,字符串切割得到需要的前半截儿url,任务列表用列表生成式。,然后就是等待 asyncio.gather就是开始执行task1里面的任务了,返回值交给resu。然后创建一个字典,用for循环,把返回的列表里面每个小的字典,添加到dic_content里面,关闭异步会话,到这里异步操作结束然后就是lamba表达式排序,用的就是num字段,这样就有一个有顺序的字典了,然后for循环依次写入文件。
8.main()函数就是启动异步


浙公网安备 33010602011771号