2. 其他库推荐

2.1. aiohttp-requests

这个库时对aiohttp库的网络请求模块的封装,用了这个库,在异步网络请求的时候,可以在写法上更简洁易懂。本质上还是aiohttp库的使用。推荐使用这个库来做网络请求。

2.2. aiofiles

aiofiles是一个用Python编写,用于处理asyncio应用程序中的本地磁盘文件。爬虫过程中用它来进行文件的异步操作。

2.3. grequests

grequests模块相当于是封装了gevent的requests模块。

3. 问题记录

3.1. Multipart.FormData 示例

下面示例展示上传图片至SM.MS。

with open(abspath_file, 'rb') as fp:
    multipart_form_data = aiohttp.FormData(quote_fields=False)  # quote_fields: 将对中文进行转码
    multipart_form_data.add_field('smfile', fp,
                                  content_type="image/jpeg",
                                  filename=os.path.basename(relpath_file),
                                  content_transfer_encoding="base64")
    headers = {'Authorization': self.api_token} if self.api_token else None
    # headers = {"Content-Type": "multipart/form-data"}
    async with aiohttp.ClientSession() as session:
        async with session.post(self.endpoint,
                                data=multipart_form_data,
                                headers=headers) as resp:
            await resp.text()
            str_response = await resp.text()
            json_content = json.loads(str_response)
            if not json_content['success']:
                logger.error(json_content)
                raise UploadError()
            print(f"[+] 完成上传: {relpath_file}")

3.2. with open("xxx") 会被自动关闭

程序是这样的:

with open("xxx", "rb") as fp:
    async with aiohttp.ClientSession() as session:
        async with session.post(self.endpoint, data=fp) as resp:
            await resp.text()
    file_hash = hashlib.md5()
    while chunk := fp.read(8192):  # 这里报错:ValueError: read of closed file
        file_hash.update(chunk)
    return file_hash.hexdigest()

报错:ValueError: read of closed file

找到一篇相似的文章,解释不保证准确:

问题是open(...)返回一个文件对象,并且您要将同一文件对象传递给要start()在顶层创建的所有协程。恰好先调度的协程实例将文件对象session.post()作为的一部分传输data,session.post()并将读取文件到最后并关闭文件对象。下一个start()协程将尝试从现在关闭的对象中读取,这将引发异常。

要解决此问题而不多次打开文件,您需要确保实际将数据作为字节对象读取:

data = {'file': open('test_img.jpg', 'rb').read()}

这会将相同的字节对象传递给所有协程,它们应按预期工作。

3.3. filename中文错误

使用post方式,上传multipart到SM.MS时,图像存储没问题,但文件名从中文变成了诸如 %E9%B2%8D%E9%B.jpg 的样子……应该是编码问题。怎么避免呢?

multipart_form_data = aiohttp.FormData(quote_fields=False)  # quote_fields: 将对中文进行转码

使用参数 quote_fields 将避免该问题。

3.4. aiohttp(yarl)对url部分字符自动urldecode

github

最新碰到一个用 aiohttp 访问不出内容,但是用 requests 能访问的情况,url 是事先进行了 urlencode 的, 下面的 url 随便找了个站点代替,但是把重点的参数提了出来

%40 对应的是 `@`
%3a 对应的是 `:`

解决方案:

str_url = "https://www.xxx.com?xxx%40yyy%3azzz"
proxy_url = "http://localhost:8080"
async with session.get(URL(str_url), proxy=proxy_url) as resp:
    print(await resp.text())
async with session.get(URL(str_url, encoded=True), proxy=proxy_url) as resp:
    print(await resp.text())

2. 其他库推荐

2.1. aiohttp-requests

这个库时对aiohttp库的网络请求模块的封装,用了这个库,在异步网络请求的时候,可以在写法上更简洁易懂。本质上还是aiohttp库的使用。推荐使用这个库来做网络请求。

2.2. aiofiles

aiofiles是一个用Python编写,用于处理asyncio应用程序中的本地磁盘文件。爬虫过程中用它来进行文件的异步操作。

2.3. grequests

grequests模块相当于是封装了gevent的requests模块。

3. 问题记录

3.1. Multipart.FormData 示例

下面示例展示上传图片至SM.MS。

with open(abspath_file, 'rb') as fp:
    multipart_form_data = aiohttp.FormData(quote_fields=False)  # quote_fields: 将对中文进行转码
    multipart_form_data.add_field('smfile', fp,
                                  content_type="image/jpeg",
                                  filename=os.path.basename(relpath_file),
                                  content_transfer_encoding="base64")
    headers = {'Authorization': self.api_token} if self.api_token else None
    # headers = {"Content-Type": "multipart/form-data"}
    async with aiohttp.ClientSession() as session:
        async with session.post(self.endpoint,
                                data=multipart_form_data,
                                headers=headers) as resp:
            await resp.text()
            str_response = await resp.text()
            json_content = json.loads(str_response)
            if not json_content['success']:
                logger.error(json_content)
                raise UploadError()
            print(f"[+] 完成上传: {relpath_file}")

3.2. with open("xxx") 会被自动关闭

程序是这样的:

with open("xxx", "rb") as fp:
    async with aiohttp.ClientSession() as session:
        async with session.post(self.endpoint, data=fp) as resp:
            await resp.text()
    file_hash = hashlib.md5()
    while chunk := fp.read(8192):  # 这里报错:ValueError: read of closed file
        file_hash.update(chunk)
    return file_hash.hexdigest()

报错:ValueError: read of closed file

找到一篇相似的文章,解释不保证准确:

问题是open(...)返回一个文件对象,并且您要将同一文件对象传递给要start()在顶层创建的所有协程。恰好先调度的协程实例将文件对象session.post()作为的一部分传输data,session.post()并将读取文件到最后并关闭文件对象。下一个start()协程将尝试从现在关闭的对象中读取,这将引发异常。

要解决此问题而不多次打开文件,您需要确保实际将数据作为字节对象读取:

data = {'file': open('test_img.jpg', 'rb').read()}

这会将相同的字节对象传递给所有协程,它们应按预期工作。

3.3. filename中文错误

使用post方式,上传multipart到SM.MS时,图像存储没问题,但文件名从中文变成了诸如 %E9%B2%8D%E9%B.jpg 的样子……应该是编码问题。怎么避免呢?

multipart_form_data = aiohttp.FormData(quote_fields=False)  # quote_fields: 将对中文进行转码

使用参数 quote_fields 将避免该问题。

3.4. aiohttp(yarl)对url部分字符自动urldecode

github

最新碰到一个用 aiohttp 访问不出内容,但是用 requests 能访问的情况,url 是事先进行了 urlencode 的, 下面的 url 随便找了个站点代替,但是把重点的参数提了出来

%40 对应的是 `@`
%3a 对应的是 `:`

解决方案:

str_url = "https://www.xxx.com?xxx%40yyy%3azzz"
proxy_url = "http://localhost:8080"
async with session.get(URL(str_url), proxy=proxy_url) as resp:
    print(await resp.text())
async with session.get(URL(str_url, encoded=True), proxy=proxy_url) as resp:
    print(await resp.text())