使用多处理来改进BeautifulSoup对维基百科的抓取



我正在使用BeautifulSoup从一堆维基百科页面上抓取一些基本信息。程序运行缓慢(650页大约20分钟(。我正试图使用多处理来加快速度,但它并没有像预期的那样工作。它要么看起来被卡住了,什么都不做,要么只会刮到每页名称的第一个字母。

我使用的抓取代码是:

#dict where key is person's name and value is proper wikipedia url formatting
all_wikis = { 'Adam Ferrara': 'Adam_Ferrara',
'Adam Hartle': 'Adam_Hartle',
'Adam Ray': 'Adam_Ray_(comedian)',
'Adam Sandler': 'Adam_Sandler',
'Adele Givens': 'Adele_Givens'}
bios = {}
def scrape(dictionary):
for key in dictionary:
#search each page
page = requests.get(("https://en.wikipedia.org/wiki/" + str(key)))
data = page.text
soup = BeautifulSoup(data, "html.parser")
#get data
try:
bday = soup.find('span', attrs={'class' : 'bday'}).text
except:
bday = 'Birthday Unknown'
try:
birthplace = soup.find('div', attrs={'class' : 'birthplace'}).text
except:
birthplace = 'Birthplace Unknown'
try:
death_date = (soup.find('span', attrs={'style' : "display:none"}).text
      .replace("(", "")
      .replace(")", ""))
living_status = 'Deceased'
except:
living_status = 'Alive'
try:
summary = wikipedia.summary(dictionary[key].replace("_", " "))
except:
summary = "No Summary"
bios[key] = {}
bios[key]['birthday'] = bday
bios[key]['home_town'] = birthplace
bios[key]['summary'] = summary
bios[key]['living_status'] = living_status
bios[key]['passed_away'] = death_date

我试着用下面的代码在脚本的末尾添加处理,但它不起作用,或者只提取每个页面的第一个字母(例如,如果我搜索的页面是李小龙,它会在维基百科页面上提取字母B,然后抛出一堆错误(。

from multiprocessing import Pool, cpu_count
if __name__ == '__main__':
pool = Pool(cpu_count())
results = pool.map(func=scrape, iterable=all_wiki)
pool.close()
pool.join()

有没有更好的方法来构建我的脚本以允许多处理?

这里有几个问题:

  • dictionaryall_wikisdict中的每个字符串键。当您使用for key in dictionary:迭代该字符串时,它会访问字符串中的每个字符。您的第一个请求是https://en.wikipedia.org/wiki/A,这不是期望的结果
  • 即使dictionary是一个名字,str(key)也没有真正的帮助。我们需要使用all_wikis[name]查找正确的URL。顺便说一句,避免使用像dictionary这样的通用变量
  • 由于您是多处理的,所以像bios这样的数据需要共享才能进行操作。最简单的方法是只使用map函数的返回值,它是所有辅助函数返回值的集合
  • 您的抓取存在逻辑问题。wikipedia.summary未定义。在不确定你想要的确切结果的情况下,它报道亚当·桑德勒已经去世。我将把这个问题留给读者练习,因为这个问题主要是关于多处理的
  • 我不确定多处理是否像多线程一样令人满意。由于你的进程99%的时间都会被阻止发出请求,我敢打赌,你可以使用比核心数量多得多的线程(或进程(来提高效率。当你在做与CPU相关的工作时,多处理器更适合,但这里的情况并非如此;在Python进程本身上实际花费的时间非常少。我建议通过增加进程(或者线程,如果你为此进行重构的话(来测试代码,直到你不再看到改进

下面是一些让您入门的代码。根据你的例子,我坚持多处理,没有调整网络抓取逻辑。

import requests
from bs4 import BeautifulSoup
from multiprocessing import Pool, cpu_count
all_wikis = {'Adam Ferrara': 'Adam_Ferrara',
'Adam Hartle': 'Adam_Hartle',
'Adam Ray': 'Adam_Ray_(comedian)',
'Adam Sandler': 'Adam_Sandler',
'Adele Givens': 'Adele_Givens'}
def scrape(name):
data = requests.get("https://en.wikipedia.org/wiki/" + all_wikis[name]).text
soup = BeautifulSoup(data, "html.parser")
bio = {}
try:
bio['birthday'] = soup.find('span', attrs={'class': 'bday'}).text
except:
bio['birthday'] = 'Birthday Unknown'
try:
bio['home_town'] = soup.find('div', attrs={'class': 'birthplace'}).text
except:
bio['home_town'] = 'Birthplace Unknown'
try:
bio['passed_away'] = (soup.find('span', attrs={'style': "display:none"}).text
  .replace("(", "")
  .replace(")", ""))
bio['living_status'] = 'Deceased'
except:
bio['living_status'] = 'Alive'
bio['summary'] = "No Summary"
return name, bio

if __name__ == '__main__':
pool = Pool(cpu_count())
bios = dict(pool.map(func=scrape, iterable=all_wikis))
pool.close()
pool.join()
print(bios)

最新更新