Python中使用asyncio调度协程优化网页表格爬取问题
问题分析与解决
你的代码存在两个核心问题:
asyncio.create_task()返回的是Future对象,必须通过await获取实际结果,直接传给pd.DataFrame()必然报错。- Selenium的WebDriver API是完全同步阻塞的,
driver.find_element()这类方法不是协程函数,用await和asyncio.gather()包装同步代码不仅无法实现异步并行,还会导致逻辑混乱。
要实现表格数据的高效抓取,以下是两种可行方案:
方案一:用原生支持异步的Playwright(推荐)
Playwright是新一代浏览器自动化工具,原生支持asyncio,能真正并行处理元素查询,性能远优于Selenium+asyncio的组合。
示例代码:
import asyncio from playwright.async_api import async_playwright import pandas as pd async def main(): async with async_playwright() as p: browser = await p.chromium.launch(headless=True) page = await browser.new_page() await page.goto("你的目标网页URL") # 等待表格加载完成 await page.wait_for_selector('table tbody') # 异步批量获取行数据 rows = await page.query_selector_all('table tbody tr') data = [] for row in rows: cells = await row.query_selector_all('td') # 并行获取单元格文本 row_data = await asyncio.gather(*[cell.text_content() for cell in cells]) data.append(row_data) # 生成DataFrame names = ['A', 'B', 'C', 'D', 'E', 'F','G', 'H', 'I'] df = pd.DataFrame(data, columns=names) print(df.head()) await browser.close() asyncio.run(main())
方案二:Selenium结合线程池并行抓取
如果必须使用Selenium,由于它不支持asyncio原生异步,可以用线程池并行处理每行抓取(注意:每个线程需使用独立的WebDriver实例,避免线程安全问题)。
示例代码:
from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC import pandas as pd from concurrent.futures import ThreadPoolExecutor def fetch_row(driver_url, row_index, ncols): # 每个线程创建独立的driver实例 driver = webdriver.Remote(command_executor=driver_url) try: # 一次性获取整行所有单元格,减少浏览器交互次数 cells = driver.find_elements(By.XPATH, f'//table/tbody/tr[{row_index}]/td') return [cell.text for cell in cells] finally: driver.quit() def main(): # 先初始化driver获取表格行列数,再启动远程服务供线程复用 driver = webdriver.Chrome() driver.get("你的目标网页URL") WebDriverWait(driver, 10).until(EC.presence_of_all_elements_located((By.XPATH, '//table/tbody'))) nrows = len(driver.find_elements(By.XPATH, '//table/tbody/tr')) ncols = len(driver.find_elements(By.XPATH, '//table/tbody/tr[1]/td')) driver_url = driver.command_executor._url driver.quit() # 线程池并行抓取 with ThreadPoolExecutor(max_workers=10) as executor: futures = [executor.submit(fetch_row, driver_url, i+1, ncols) for i in range(nrows)] data = [future.result() for future in futures] # 生成DataFrame names = ['A', 'B', 'C', 'D', 'E', 'F','G', 'H', 'I'] df = pd.DataFrame(data, columns=names) print(df.head()) if __name__ == "__main__": main()
额外优化建议
- 优先用批量查询(如
find_elements一次性获取整行单元格)替代逐个单元格的XPATH查询,减少浏览器交互次数,大幅提升速度。 - Selenium开启headless模式、禁用图片加载等配置,也能进一步缩短抓取时间。
内容的提问来源于stack exchange,提问作者Ivan
相关产品推荐
相关产品推荐

