python多任务异步爬虫ConnectionAbortedError报错


# -*- coding: utf-8 -*-
"""
Created on Sat May 20 15:02:58 2023

@author: JUJU
"""

from bs4 import BeautifulSoup
import json
import os
import asyncio
import aiohttp
import pandas as pd
import nest_asyncio
import pymysql

#设置当前工作文件夹
path='D:/pycharm/PyCharm 2023.1/project/wlpc/notes/Journals/国际贸易问题'
os.chdir(path)
nest_asyncio.apply()

#生成检索目录与url链接
year=2023
end_year=2015
volumnlist=[]
urls=[]
while year>=end_year:
    month=12
    if year==2023:
        month=3
        while month>0:
            item=str(year)+'年 第'+str(month)+'期'
            if month>=10:
                url='https://gjmw.cbpt.cnki.net/WKC/WebPublication/wkTextContent.aspx?colType=4&yt='+str(year)+'&st='+str(month)
            else:
                url='https://gjmw.cbpt.cnki.net/WKC/WebPublication/wkTextContent.aspx?colType=4&yt='+str(year)+'&st=0'+str(month)
            urls.append(url)
            volumnlist.append(item)
            month-=1
    else:
        while month >0:
            item=str(year)+'年 第'+str(month)+'期'
            if month>=10:
                url='https://gjmw.cbpt.cnki.net/WKC/WebPublication/wkTextContent.aspx?colType=4&yt='+str(year)+'&st='+str(month)
            else:
                url='https://gjmw.cbpt.cnki.net/WKC/WebPublication/wkTextContent.aspx?colType=4&yt='+str(year)+'&st=0'+str(month)
            urls.append(url)
            volumnlist.append(item)
            month-=1
    year-=1
  
#保存目录与url链接  
with open('国际贸易问题(目录及链接).json','w',encoding='utf8') as f:
    volum_dic=dict(zip(volumnlist,urls))
    v_json = json.dumps(volum_dic,sort_keys=False, indent=4, ensure_ascii=False,separators=(',', ': '))
    f.write(v_json)

#反馈    
print('国际贸易问题(目录及链接).json存储完成')

table=[]

# 获取网页(文本信息)
async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text(encoding='utf-8')

async def parse(text):#解析网页
    
    soup=BeautifulSoup(text,'lxml')
    
    a=soup.select('#e3 .column_titbox_title a')
    
    b=a[0].text
    print(b)
    year=int(b[0:4])
    
    month=int(b[5:7])
    
    tags=soup.find('ul', class_="column_contbox_zxlist")('li')
        
    for tag in tags:
        tag_h3=tag.find('h3')
        title=tag_h3.text
        
        href=tag_h3.a['href']
        
        abt='https://gjmw.cbpt.cnki.net/WKC'+str(href[2:])
        #摘要链接
        
        tag_samp=tag.find('samp')
        authors=tag_samp.text
        tag_span=tag.find('span')('a')[2]
        href=tag_span['href']
        
        download_link='https://gjmw.cbpt.cnki.net/WKC'+str(href[2:])
        #文章下载链接
        
        nub=tag_span=tag.find('span')('font')[0]
        
        number=nub.text
        
        table.append([year,month,title,authors,abt,download_link,number])
        
    return table
               
    
async def download(url):
    
    async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(limit=64,verify_ssl=False)) as session:

        html = await fetch(session, url)
        await parse(html)
    
tasks = []

loop = asyncio.get_event_loop()
tasks = [asyncio.ensure_future(download(url)) for url in urls]
tasks = asyncio.gather(*tasks)
loop.run_until_complete(tasks)

df = pd.DataFrame(table, columns=['年份','月份','标题','作者','摘要链接','下载链接','下载次数'])
df.to_csv('./国际贸易问题.csv',index=False)
print('数据导出为csv成功!')

conn=pymysql.connect(host="localhost",user="root",password='root',
                  cursorclass=pymysql.cursors.DictCursor)
try:
    # 创建游标
    cur = conn.cursor()
    # 执行sql查询语句
 
    cur.execute("create database Jounrals")
    print('数据库创建成功')
    # 关闭游标
    cur.close()
    # 关闭数据库连接
    conn.close()
except pymysql.err.MySQLError as _error:
    print('数据库创建失败')
    raise _error

try:
    db=pymysql.connect(host="localhost",user="root",password='root',
                      database='Jounrals')
        
    df.to_sql("国际贸易问题",db,if_exists="append")
    print('数据库写入成功')
except:
    print('数据库写入失败')

print('任务完成\n','*'*20)

img

img

以下答案由GPT-4.5Plus大模型(语音)与博主A努力中.......共同编写:
这似乎是一个使用asyncio模块进行异步爬虫时出现的ConnectionAbortedError。这通常是因为建立的连接过多导致的。

asyncio模块使用的是协程,每个协程在等待I/O操作时可以让出控制权,所以可以实现高并发。但是,协程本身并不是线程,它们共享同一个事件循环,因此也共享同一个网络连接池。如果协程数量过多,会导致网络连接池中所有的连接都被占用,新的连接无法建立,此时会抛出ConnectionAbortedError。

一些解决方法如下:

  1. 减少最大连接数。可以在aiohttp.ClientSession对象中设置connector_owner=False和limit参数来限制最大连接数,如:
python
async with ClientSession(connector_owner=False, limit=100) as session:
    ...


  1. 增大连接池大小。可以增加aiohttp的连接池大小,如:
python
aiohttp.tcp_connector.TCPConnector(limit=200)

  1. 使用Semaphore限流。可以使用asyncio.Semaphore来限制协程的数量,如:
python
sem = asyncio.Semaphore(100)

async def crawler():
    async with sem:
        ...

这将限制同时运行的协程数量为100。

  1. 使用异步锁。也可以使用asyncio.Lock来实现异步锁,在请求连接前先获取锁,释放锁后关闭连接,以达到限制连接数的目的。

  2. 协程内重试。在协程内catch住ConnectionError并重试,以避免新的连接无法建立。

  3. 使用线程池。可以使用线程池来发起连接,然后把连接交给asyncio的事件循环,这可以避免asyncio的事件循环直接创建过多连接。

很遗憾没有重现问题。跑了n多遍都是正常的。
倒是有个ssl错误,aiohttp版本__version__ = "3.8.4"

async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(limit=64, ssl=False)) as session:

img

ConnectionAbortedError : SSL handshake is taking longer than 60.0 seconds : aborting the connection
连接中止错误:SSL握手耗时超过60.0秒:正在中止连接

根据报错中文释义,可以分析是SSL握手超时导致链接中断
下面是可能原因和思路建议:
1、首先检查网络连接问题
思路:尝试重新连接到网络,或使用其他稳定网络再重新进行测试。
2、防火墙问题
思路:检查防火墙是否有阻止、拦截 ssl 
3、系统资源不足
思路:检查下电脑运行盘服是否有足够的内存和处理器资源来运行程序
如果上面的尝试确认OK,可以使用其他思路:通过try except 语句来定位捕获异常、通过多次重连,跳过报错、减少任务等

网络连接问题、防火墙问题、系统资源问题、代理、异常捕获、增加重试机制、增大线程池和减少最大连接数量都尝试过,依旧没有解决这个问题。

【更新】解决了,原因是由于阻塞操作没有挂起导致的

loop.run_until_complete(tasks)

改成

loop.run_until_complete(asyncio.wait(tasks))