爬取目标:原神官网新闻页面上的封面图片及其中的大图与视频
遇到的问题:原神官网视频太大,由于有时限导致任务被取消并报错TimeoutError,结果导致视频字节数据不完整即无法打开
问题出现的部分代码:
async def request_IV(self, href, special, page_str):
for src in href:
src_href = src.group('href')
self.src_href = src_href
if self.check('http'): # 检查得到的内容开头字符串是否是http
src_href = src_href.replace('\\u002F', '/')
else:
special = True
break
async with aiohttp.ClientSession() as img_session:
async with img_session.get(src_href) as picture:
# 请求网址,并写入字节数据
name = src_href.split('/')[-1]
async with aiofiles.open(f"img&video/{name}", 'wb') as f:
# 原神官网视频太大,由于有时限导致任务被取消,视频字节数据不完整即无法打开,遇到视频时保证字节写入得以正常进行
await f.write(picture.content.read())
print(f'下载地址:{src_href} 下载完成!{name}')
if special:
await special_href(page_str)
程序整个代码:
import asyncio
import aiohttp
import aiofiles
import re
# 从方法脱离出的函数:
async def cover(value, title):
if value:
cover_picture_url = value[0]['url']
# 请求封面图片网址,获取字节数据并写入:
# cover_picture = requests.get(cover_picture_url)
async with aiohttp.ClientSession() as main_session:
async with main_session.get(cover_picture_url) as cover_picture:
content = await cover_picture.content.read()
async with aiofiles.open(f'cover_img/{title}.jpg', 'wb') as cpf:
await cpf.write(content)
print(f'下载完成!地址:{cover_picture_url}标题名:{title}')
async def special_href(page_str):
text = await page_str.text()
special_obj = re.compile(r'window.+?height=.+?src=\\"(?P<href>.+?)\\" width=.+?;</script>')
if not special_obj.findall(text):
special_obj = re.compile(r'window.+?height=.+?src=\\"(?P<href>.+?)\\" style=\\"max-width.+?;</script>')
href_special_obj = special_obj.finditer(text) # text原:await page_str.text()
for src in href_special_obj:
src_href = src.group('href')
src_href = src_href.replace('\\u002F', '/')
# special = requests.get(href)
async with aiohttp.ClientSession() as special_session:
async with special_session.get(src_href) as special:
# 请求视频或图片网址,并写入字节数据
name = src_href.split('/')[-1]
async with aiofiles.open(f"img&video/{name}", 'wb') as pf:
await pf.write(await special.content.read())
print(f'下载地址:{src_href} 下载完成!{name}')
class GenshinImgVideo:
def __init__(self, url: str):
self.url = url
# 在后期运行时发现存在的问题:
self.problem = None
# 保存匹配到的内容,以检查是否正确:
self.src_href = None
# 问题处理
def check(self, goal_str: str):
goal = self.src_href[0:4]
# 检查所获取的网址内容前四位是否是目标字符
if goal_str == goal:
return True
else:
return False
async def get_data_list(self):
tasks = []
url = self.url
# resp = requests.get(url)
async with aiohttp.ClientSession() as data_session:
async with data_session.get(url) as resp:
all_dic = await resp.json()
dicA = all_dic['data']['list']
# 开始查获相关数据
for dic in dicA:
contentId = dic['contentId']
title = dic['title']
value = dic['ext'][1]['value']
task = asyncio.create_task(self.main(contentId, title, value))
tasks.append(task)
await asyncio.wait(tasks)
async def request_IV(self, href, special, page_str):
for src in href:
src_href = src.group('href')
self.src_href = src_href
if self.check('http'):
src_href = src_href.replace('\\u002F', '/')
else:
special = True
break
async with aiohttp.ClientSession() as img_session:
async with img_session.get(src_href) as picture:
# 请求网址,并写入字节数据
name = src_href.split('/')[-1]
async with aiofiles.open(f"img&video/{name}", 'wb') as f:
# 原神官网视频太大,由于有时限导致任务被取消,视频字节数据不完整即无法打开,遇到视频时保证字节写入得以正常进行
await f.write(picture.content.read())
print(f'下载地址:{src_href} 下载完成!{name}')
if special:
await special_href(page_str)
async def enter_to_find(self, contentId):
# 开始进入子页面
child_page_url = f'https://ys.mihoyo.com/main/news/detail/{contentId}'
self.problem = child_page_url # 应对后期发现的某个问题
# page_str = requests.get(child_page_url)
async with aiohttp.ClientSession() as child_session:
async with child_session.get(child_page_url) as page_str:
# 寻找文件名并合成视图请求网址
special = False
obj_img = re.compile(r'window.+?src=\\"(?P<href>.+?)\\" width=.+?;</script>')
obj_video = re.compile(r'window.+?src=\\"(?P<href>.+?)\\" style=\\"max-width.+?;</script>')
obj_video_new = re.compile(r'window.+?poster=.+?src=\\"(?P<href>.+?)\\".+?……', re.S)
text = await page_str.text()
href_obj_img = obj_img.finditer(text)
href_obj_video = obj_video.finditer(text)
href_obj_video_new = obj_video_new.finditer(text)
if obj_img.findall(text):
await self.request_IV(href_obj_img, special, page_str)
elif obj_video.findall(text):
# 原神官网视频太大,由于有时限导致任务被取消,视频字节数据不完整即无法打开
await asyncio.shield(self.request_IV(href_obj_video, special, page_str))
"""但是这个地方不是问题源头"""
elif obj_video_new.findall(text):
# 原神官网视频太大,由于有时限导致任务被取消,视频字节数据不完整即无法打开
await asyncio.shield(self.request_IV(href_obj_video_new, special, page_str))
"""但是这个地方不是问题源头"""
async def main(self, contentId, title, value):
tasks = [
asyncio.create_task(cover(value, title)),
asyncio.create_task(self.enter_to_find(contentId))
]
await asyncio.wait(tasks)
async def run(self):
try:
await self.get_data_list()
except TimeoutError:
self.write_error(None)
except aiohttp.ClientPayloadError as eC:
self.write_error(eC)
else:
pass
def write_error(self, em):
if em:
print(em)
with open('Error log.txt', 'a', encoding='utf-8') as fE:
fE.write(str(em))
print(self.problem)
with open(f'log.txt', 'a', encoding='utf-8') as file:
file.write(self.problem)
"""
# 对于后期发现的问题,打印以显示,并保存到日志文件中
def puts_problem(self, num_time: int):
print(self.problem)
with open(f'log{num_time}.txt', 'w', encoding='utf-8') as file:
file.write(self.problem)"""
async def main(number: int):
tasks = []
for i in range(1, number+1):
url = f'https://content-static.mihoyo.com/content/ysCn/getContentList?pageSize=5&pageNum={i}&channelId=10'
exe = GenshinImgVideo(url)
task = asyncio.create_task(exe.run())
tasks.append(task)
await asyncio.wait(tasks)
if __name__ == '__main__':
times = input('请输入所翻页数:')
try:
num = int(times)
except ValueError:
print('请输入数字!')
else:
asyncio.run(main(num))
有人说说用asyncio.shield()进行封装,保证任务不会被取消,但我不太会用,封装picture.content.read()结果一样,封装f.write(picture.content.read())又报TypeError错误,所以谢谢各位帮我解决这个问题了
我根据你的逻辑没有找到原神视频url,不知道这视频传输是什么,只能建议你这用多线程试试看,另外我发现你这所有的地方异步,但是实际上只需要异步下载图片和视频那里就行了,需要运用一个生产者-多个消费者模型。
我一般就用线程池爬取,只要能够获取到准确的数据就行了,效率高低不太重要,快了被封或者把别人服务器搞崩,那是要喝茶的
换个配置高一点的电脑,估计是系统速度太慢,导致的!
TimeoutError通常是由于网络请求超时导致的。在异步爬虫中,可以通过以下方法来解决TimeoutError:
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=60)) as session:
。这里的timeout参数表示整个请求的超时时间,total参数表示连接、读取和写入的超时时间。async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(limit=100)) as session:
。这里的limit参数表示最大连接数。你在处理原神官网视频时遇到的超时问题可以尝试使用asyncio.shield()进行封装,以确保任务不会被取消。下面是修改后的代码示例:
async def request_IV(self, href, special, page_str):
for src in href:
src_href = src.group('href')
self.src_href = src_href
if self.check('http'):
src_href = src_href.replace('\\u002F', '/')
else:
special = True
break
async with aiohttp.ClientSession() as img_session:
async with img_session.get(src_href) as picture:
# 请求网址,并写入字节数据
name = src_href.split('/')[-1]
async with aiofiles.open(f"img&video/{name}", 'wb') as f:
# 使用 asyncio.shield() 封装,确保任务不会被取消
content = await picture.content.read()
await asyncio.shield(f.write(content))
print(f'下载地址:{src_href} 下载完成!{name}')
if special:
await special_href(page_str)
这样修改后,封装的部分f.write(content)不会被取消,并且可以正常写入字节数据。希望这能解决你遇到的问题。如果还有其他疑问,请随时向我提问。
运行结果
有没有想过分片下载呢
from retrying import retry
源码部分:
def __init__(self,
stop=None, wait=None,
stop_max_attempt_number=None,
stop_max_delay=None,
wait_fixed=None,
wait_random_min=None, wait_random_max=None,
wait_incrementing_start=None, wait_incrementing_increment=None,
wait_exponential_multiplier=None, wait_exponential_max=None,
retry_on_exception=None,
retry_on_result=None,
wrap_exception=False,
stop_func=None,
wait_func=None,
wait_jitter_max=None):
stop_max_attempt_number:用来设定最大的尝试次数,超过该次数就停止重试
stop_max_delay:比如设置成10000,那么从被装饰的函数开始执行的时间点开始,到函数成功运行结束 或者失败报错中止的时间点,只要这段时间超过10秒,函数就不会再执行了
wait_fixed:设置在两次retrying之间的停留时间
wait_random_min和wait_random_max:用随机的方式产生两次retrying之间的停留时间
wait_exponential_multiplier和wait_exponential_max:以指数的形式产生两次retrying之间的停留时间,产 生的值为2^previous_attempt_number * wait_exponential_multiplier,previous_attempt_number是前面已经retry的次数,如果产生的这个值超过了wait_exponential_max的大小,那么之后两个retrying之间的停留值都为wait_exponential_max
import asyncio
import aiohttp
import aiofiles
import re
async def request_IV(self, href, special, page_str):
for src in href:
src_href = src.group('href')
self.src_href = src_href
if self.check('http'):
src_href = src_href.replace('\\u002F', '/')
else:
special = True
break
async with aiohttp.ClientSession() as img_session:
async with img_session.get(src_href) as picture:
# 使用asyncio.shield()封装picture.content.read(),确保字节数据完整写入
content = await asyncio.shield(picture.content.read())
name = src_href.split('/')[-1]
async with aiofiles.open(f"img&video/{name}", 'wb') as f:
await f.write(content)
print(f'下载地址:{src_href} 下载完成!{name}')
if special:
await special_href(page_str)
你可以使用asyncio.shield()函数来保护任务不被取消。这样,在遇到视频字节数据不完整导致任务被取消的情况下,你可以封装picture.content.read()方法,确保字节数据的完整写入。
从上面答友可以看出,排除软硬件因素,硬件的话更新更高的配置以适应你爬取的超大视频文件,软件方面的尝试更改新的爬取方式:
在爬取的时候禁用图片加载功能,仅获取图片的URL地址,这样可以避免因为图片加载导致页面卡顿或者超时。
又或者是在爬取过程中,可以考虑优化爬取策略,如采用分布式爬取、限制请求频率、随机化请求头等,以避免被目标网站识别和封禁。
增加时限或者设置特定超时时限,或者用线程处理
有没有可能是原神服务器那边的反爬机制
你尝试更换 ua和cookie 之类的试试,或者加代理池?
增加连接超时时限
可以把视频链接弄出来后,用迅雷下载
我能想到的是这两个方法
1增加重试连接次数
#设置重连次数
requests.adapters.DEFAULT_RETRIES = 15
# 设置连接活跃状态为False
s = requests.session()
s.keep_alive = False # 在连接时关闭多余连接
2限制爬取速度
DOWNLOAD_DELAY = 0.5
RANDOMIZE_DOWNLOAD_DELAY = True # 随机的等待时间
CONCURRENT_REQUESTS = 1 # Scrapy框架同时请求的最大数量