繁体   English   中英

python webscraping使用多处理和列表理解非常慢

[英]python webscraping using multiprocessing and list comprehension very slow

我尝试使用 bs4 抓取网站。 但是代码非常非常慢,因为有很多tr要一一获取。

  1. 我尝试了循环,但需要 3 多分钟才能刮掉 3000 tr(1000 行):
ROWS = []
#soup.select('tr[id^=mix]') is a list of html elements
for tr in soup.select('tr[id^=mix]'): 
    dt = tr.select_one('.h').text 
    H_team = tr.select_one('td.Home').text.strip()
    A_team = tr.select_one('td.Away').text.strip()
    #....

    row = [dt, H_team, A_team, ...]    
    ROWS.append(row)
    print(row)
  1. 我尝试了List Comprehension但没有改变速度(甚至更慢):
def my_funct(tr):
    dt = tr.select_one('.h').text 
    H_team = tr.select_one('td.Home').text.strip()
    A_team = tr.select_one('td.Away').text.strip()
        
    row = [dt, H_team, A_team]    
    return row 

ROWS = [my_funct(tr) for tr in soup.select('tr[id^=mix]')]
  1. 我尝试了多处理模块,但速度是一样的
from multiprocessing.dummy import Pool as ThreadPool

def my_funct(tr):
    dt = tr.select_one('.h').text 
    H_team = tr.select_one('td.Home').text.strip()
    A_team = tr.select_one('td.Away').text.strip()
        
    row = [dt, H_team, A_team]    
    return row 

pool = ThreadPool(4)
ROWS = pool.map(my_funct, soup.select('tr[id^=mix]'))

pool.close()
pool.join()
  1. 我试过asyncio ,但没有用(返回错误)
import asyncio

async def my_funct(tr):
    dt = tr.select_one('.h').text 
    H_team = tr.select_one('td.Home').text.strip()
    A_team = tr.select_one('td.Away').text.strip()
        
    row = [dt, H_team, A_team]    
    return row 

async def s():
    await asyncio.gather(*[my_funct(tr) for tr in soup.select('tr[id^=Today]')]) 

asyncio.run(s())

#return error: "RuntimeError: asyncio.run() cannot be called from a running event loop"

如何并行运行行的抓取,以便我的代码不需要很长时间来逐行处理每一行?

在性能方面,通常有两个主要原因导致可能存在计算或 I/O 瓶颈。

我假设网页在抓取过程中已完全加载,这将消除网络 I/O 的问题。 如果不是这样,并且被抓取的网页是分页的,最好先将所有这些页面缓存在内存中,以提高处理时的性能。

看起来您已尝试使用线程执行多处理。 线程使用它们所属进程的相同内存,这很好,因为它减少了进程间通信开销。 然而,由于 python 的全局解释器锁,这不会提高 cpu 绑定 python 应用程序的性能,因为一次运行的单个线程会限制工作负载。

它在您的数据集上执行较慢的事实是预期的,因为现在在线程之间的管理和上下文切换方面有一点开销。

尝试切换:

pool = ThreadPool(4)

pool = Pool(4) # number of processors available

使用不同数量的处理器对较小的数据集进行基准测试可能有助于确定最佳数量。

由于您不共享数据,我只能猜测:您尝试解析具有许多 N 列和 M=3000 行的大表。

您当前的实现调用select_one("td...") M x N 次。 这可能是使您的代码变慢的原因。

您可以尝试什么:通过一次选择获取每一行。

ls = []
for tr in soup.select('tr[id^=mix]'): 
    row = [td.get_text(strip=True) for td in tr.select('td')]
    ls.append(row)
df = pd.Dataframe(ls, columns=["...in the right order.."])

这是一个基准

def generate_html_table(n_row, n_col):
    tds = [f'<td class="c{i}">{i}</td>' for i in range(n_col)]
    tr = "<tr>" + "".join(tds) + "</tr>"
    table = "<table>" + tr * n_row + "</table>"
    return table

## Generate a html table of 1000 x 20, with class attribute for each column
M, N = 1000, 20
soup = BeautifulSoup(generate_html_table(M, N))

columns = [f"col_{i}" for i in range(N)]

# OP approach: one .select_one for each td
def parse_table_1(soup):
    rows = []
    for tr in soup.select('tr'): 
        row = [tr.select_one(f'td.c{i}').get_text(strip=True) for i in range(N)]
        rows.append(row)
    return pd.DataFrame(rows, columns=columns)

# Proposed approach: one .select for each row
def parse_table_2(soup):
    rows = []
    for tr in soup.select('tr'): 
        row = [td.get_text(strip=True) for td in tr.select("td")]
        rows.append(row)
    return pd.DataFrame(rows, columns=columns)

结果: 1000 x 20 表的速度提高了 10 倍

%timeit parse_table_1(soup)
3.69 s ± 328 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit parse_table_2(soup)
351 ms ± 39.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

对于 1000 x 100 表,速度提高了 35 倍

1min 9s ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
1.88 s ± 175 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM