Как реализовать многопроцессорность в моем BeautifulSoup WebScraper - PullRequest
0 голосов
/ 23 января 2020

Я сделал веб-скребок с Python и BeautifulSoup lib, и он работает нормально, единственное, что он очень медленный. Итак, теперь я хочу реализовать некоторую многопроцессорность, чтобы ускорить ее, но я не знаю, как.

Мой код состоит из двух пар. Первая часть - это очистка веб-сайта, чтобы я мог генерировать URL-адреса, которые я хочу продолжить, и добавление этих URL-адресов в список. Первая часть выглядит следующим образом:

from bs4 import BeautifulSoup
import requests
from datetime import date, timedelta
from multiprocessing import Pool

headers = {'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36'}

links = [["Cross-Country", "https://www.fis-ski.com/DB/cross-country/cup-standings.html", "?sectorcode=CC&seasoncode={}&cupcode={}&disciplinecode=ALL&gendercode={}&nationcode="],
         ["Ski Jumping", "https://www.fis-ski.com/DB/ski-jumping/cup-standings.html", ""],
         ["Nordic Combined", "https://www.fis-ski.com/DB/nordic-combined/cup-standings.html", ""],
         ["Alpine", "https://www.fis-ski.com/DB/alpine-skiing/cup-standings.html", ""]]

# FOR LOOP FOR GENERATING URLS FOR SCRAPPING

all_urls = []
for link in links[:1]:

    response = requests.get(link[1], headers = headers)
    soup = BeautifulSoup(response.text, 'html.parser')

    discipline = link[0]
    print(discipline)

    season_list = []
    competition_list = []
    gender_list = ["M", "L"]


    all_seasons = soup.find_all("div", class_ = "select select_size_medium")[0].find_all("option")
    for season in all_seasons:
        season_list.append(season.text)

    all_competitions = soup.find_all("div", class_ = "select select_size_medium")[1].find_all("option")
    for competition in all_competitions:
        competition_list.append([competition["value"], competition.text])


    for gender in gender_list:
        for competition in competition_list[:1]:
            for season in season_list[:2]:

                url = link[1] + link[2].format(season, competition[0], gender)
                all_urls.append([discipline, season, competition[1], gender, url])

                print(discipline, season, competition[1], gender, url)
                print()

print(len(all_urls))   

Эта первая часть создает более 4500 ссылок, но я добавил некоторые ограничения индекса, чтобы он генерировал только 8 ссылок. Это вторая часть кода, это функция, которая в основном предназначена для l oop, которая идет по URL-адресу и выполняет очистку определенных c данных. Вторая часть:

# FUNCTION FOR SCRAPPING
def parse():
    for url in all_urls:

        response = requests.get(url[4], headers = headers)
        soup = BeautifulSoup(response.text, 'html.parser')

        all_skier_names = soup.find_all("div", class_ = "g-xs-10 g-sm-9 g-md-4 g-lg-4 justify-left bold align-xs-top")
        all_countries = soup.find_all("span", class_ = "country__name-short")


        discipline = url[0]
        season = url[1]
        competition = url[2]
        gender = url[3]


        for name, country in zip(all_skier_names , all_countries):

            skier_name = name.text.strip().title()
            country = country.text.strip()

            print(discipline, "|", season, "|", competition, "|", gender, "|", country, "|", skier_name)

        print()

parse() 

Я прочитал некоторую документацию о том, что моя многопроцессорная часть должна выглядеть так:

p = Pool(10)  # Pool tells how many at a time
records = p.map(parse, all_urls)
p.terminate()
p.join()  

Но я запустил это и подождал 30 минут, и ничего не произошло. Что я делаю не так, как я могу реализовать многопроцессорность с пулом, чтобы я мог очистить 10 или более URL одновременно?

Ответы [ 2 ]

1 голос
/ 23 января 2020

Вот простая реализация с multiprocessing.Pool. Обратите внимание, я использовал модуль tqdm, чтобы показать хороший индикатор выполнения (полезно посмотреть, каков текущий прогресс в долго работающих программах):

from bs4 import BeautifulSoup
import requests
from datetime import date, timedelta
from multiprocessing import Pool
import tqdm

headers = {'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36'}

def parse(url):
    response = requests.get(url[4], headers = headers)
    soup = BeautifulSoup(response.text, 'html.parser')

    all_skier_names = soup.find_all("div", class_ = "g-xs-10 g-sm-9 g-md-4 g-lg-4 justify-left bold align-xs-top")
    all_countries = soup.find_all("span", class_ = "country__name-short")

    discipline = url[0]
    season = url[1]
    competition = url[2]
    gender = url[3]

    out = []
    for name, country in zip(all_skier_names , all_countries):
        skier_name = name.text.strip().title()
        country = country.text.strip()
        out.append([discipline, season,  competition,  gender,  country,  skier_name])

    return out

# here I hard-coded all_urls:
all_urls = [['Cross-Country', '2020', 'World Cup', 'M', 'https://www.fis-ski.com/DB/cross-country/cup-standings.html?sectorcode=CC&seasoncode=2020&cupcode=WC&disciplinecode=ALL&gendercode=M&nationcode='], ['Cross-Country', '2020', 'World Cup', 'L', 'https://www.fis-ski.com/DB/cross-country/cup-standings.html?sectorcode=CC&seasoncode=2020&cupcode=WC&disciplinecode=ALL&gendercode=L&nationcode='], ['Ski Jumping', '2020', 'World Cup', 'M', 'https://www.fis-ski.com/DB/ski-jumping/cup-standings.html'], ['Ski Jumping', '2020', 'World Cup', 'L', 'https://www.fis-ski.com/DB/ski-jumping/cup-standings.html'], ['Nordic Combined', '2020', 'World Cup', 'M', 'https://www.fis-ski.com/DB/nordic-combined/cup-standings.html'], ['Nordic Combined', '2020', 'World Cup', 'L', 'https://www.fis-ski.com/DB/nordic-combined/cup-standings.html'], ['Alpine', '2020', 'World Cup', 'M', 'https://www.fis-ski.com/DB/alpine-skiing/cup-standings.html'], ['Alpine', '2020', 'World Cup', 'L', 'https://www.fis-ski.com/DB/alpine-skiing/cup-standings.html']]

with Pool(processes=2) as pool, tqdm.tqdm(total=len(all_urls)) as pbar: # create Pool of processes (only 2 in this example) and tqdm Progress bar
    all_data = []                                                       # into this list I will store the urls returned from parse() function
    for data in pool.imap_unordered(parse, all_urls):                   # send urls from all_urls list to parse() function (it will be done concurently in process pool). The results returned will be unordered (returned when they are available, without waiting for other processes)
        all_data.extend(data)                                           # update all_data list
        pbar.update()                                                   # update progress bar

# Note:
# this for-loop will have 8 iterations (because all_urls has 8 links)

# print(all_data) # <-- this is your data
0 голосов
/ 23 января 2020

Код, размещенный @ andrej-kesely, прекрасно работает в режиме ожидания. Убедитесь, что код имеет правильный интервал, где должно быть

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...