You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用AsyncIO爬取REI网站时的生成器访问错误排查

解决AsyncIO爬虫中asyncio.gather结果解析的AttributeError问题

问题核心

你踩了个常见的坑:asyncio.gather(*tasks) 返回的是所有任务结果组成的列表——哪怕只爬1页,results也是[HTMLParser对象],而非单个HTMLParser实例。直接把列表传给parse_search_page,函数里调用html.css()必然触发AttributeError,因为列表根本没有css方法。另外fetch_html在请求失败时会返回False,不处理这种无效结果也会导致后续解析报错。

修正代码

重点修改main函数的结果处理逻辑,遍历results列表并过滤无效结果:

from typing import Any
import asyncio
import httpx

from selectolax.parser import HTMLParser
from urllib.parse import urljoin


async def fetch_html(client: httpx.AsyncClient, url: str, **kwargs) -> Any:
    headers = {
        "User-Agent": "替换成你的UA,否则会403"
    }

    if kwargs.get("page"):
        resp = await client.get(
            url=url + str(kwargs.get("page")), follow_redirects=True, headers=headers
        )

    else:
        resp = await client.get(url=url, follow_redirects=True, headers=headers)

    try:
        resp.raise_for_status()
    except httpx.HTTPStatusError as exc:
        print(
            f"请求出错 {exc.response.status_code},目标地址:{exc.request.url!r},已超出页面限制"
        )
        return False

    html = HTMLParser(html=resp.text)
    return html


def extract_text(html: HTMLParser, sel: str):
    try:
        text = html.css_first(sel).text()
        return text
    except AttributeError:
        return None


def parse_search_page(html: HTMLParser):
    products = html.css("li.VcGDfKKy_dvNbxUqm29K")
    for product in products:
        yield urljoin("https://www.rei.com/", product.css_first("a").attributes["href"])


async def main():
    async with httpx.AsyncClient(http2=True) as client:
        tasks = []
        rei_url = "https://www.rei.com/c/camping-and-hiking/f/scd-deals?page="
        # 这里可以修改range爬多页,比如range(1, 5)
        for i in range(1, 2):
            tasks.append(asyncio.create_task(fetch_html(client, rei_url, page=i)))
        results = await asyncio.gather(*tasks)

    # 关键改动:遍历结果列表,过滤无效值,逐个解析页面
    links = []
    for html in results:
        if html is not False:
            links.extend(parse_search_page(html))
    
    for link in links:
        print(link)


if __name__ == "__main__":
    asyncio.run(main())

额外说明

  1. 不需要把parse_search_page改成异步:HTML解析是CPU密集型操作,异步IO只适合网络请求这类IO密集型任务,同步解析反而更高效。
  2. 多页爬取兼容:如果后续要爬多页,直接修改range(1, N)即可,代码会自动收集所有页面的商品链接。
  3. 必须替换UA:网站会拦截默认UA,务必把fetch_html里的User-Agent改成你的浏览器真实UA。

内容的提问来源于stack exchange,提问作者Adrian Fletcher

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 17:40:56