[英]python webscraping using multiprocessing and list comprehension very slow
我尝试使用 bs4 抓取网站。 但是代码非常非常慢,因为有很多tr要一一获取。
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)
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]')]
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()
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.