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

Using multiprocessing to improve Wikipedia scrape with 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

我尝试使用下面的代码将处理添加到脚本的末尾,但它不起作用或只提取每个页面的第一个字母(例如,如果我正在搜索的页面是Bruce Lee,它反而会调出字母 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_wikis 字典中的每个字符串键。当您随后使用 for key in dictionary: 遍历此字符串时,这将访问字符串中的每个字符。您的第一个请求是 https://en.wikipedia.org/wiki/A,这不是您想要的结果。
  • 即使 dictionary 是一个名字,
  • str(key) 也没什么用。我们需要用 all_wikis[name] 查找正确的 URL。顺便说一句,避免像 dictionary.
  • 这样的通用变量
  • 由于您是多处理的,因此需要共享 bios 等数据才能进行操作。最简单的方法是只使用 map 函数中的 return 值,它是所有辅助函数 return 值的集合。
  • 您的抓取存在逻辑问题。 wikipedia.summary 未定义。在不确定你想要的确切结果的情况下,它报告亚当桑德勒已故。我将把它留作 reader 的练习,因为这个问题主要是关于多处理的。
  • 我不确定这里的多处理是否和多线程一样受欢迎。由于您的进程在 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)