Невозможно разрешить сценарию заполнять результаты, используя concurrent.futures настроенным способом - PullRequest
3 голосов
/ 28 апреля 2020

Я создал скрипт в python для удаления имени пользователя с целевой страницы сайта и заголовка с внутренней страницы . Я пытаюсь использовать библиотеку concurrent.futures для выполнения параллельных задач. Я знаю, как использовать executor.submit() в приведенном ниже скрипте, поэтому мне не интересно go таким образом. Я бы хотел go для executor.map(), который я уже определил (возможно, неправильно) в следующем скрипте.

Я пробовал с:

import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
import concurrent.futures as futures

URL = "https://stackoverflow.com/questions/tagged/web-scraping"
base = "https://stackoverflow.com"

def get_links(s,url):
    res = s.get(url)
    soup = BeautifulSoup(res.text,"lxml")
    for item in soup.select(".summary"):
        user_name = item.select_one(".user-details > a").get_text(strip=True)
        post_link = urljoin(base,item.select_one(".question-hyperlink").get("href"))
        yield s,user_name,post_link

def fetch(s,name,url):
    res = s.get(url)
    soup = BeautifulSoup(res.text,"lxml")
    title = soup.select_one("h1[itemprop='name'] > a").text
    return name,title

if __name__ == '__main__':
    with requests.Session() as s:
        with futures.ThreadPoolExecutor(max_workers=5) as executor:
            link_list = [url for url in get_links(s,URL)]
            for result in executor.map(fetch, *link_list):
                print(result)

Я получаю следующую ошибку при запуске вышеуказанного сценария как:

TypeError: fetch() takes 3 positional arguments but 50 were given

Если я запускаю сценарий, модифицирующий эту часть link_list = [url for url in get_links(s,URL)][0], я получаю следующую ошибку:

TypeError: zip argument #1 must support iteration

Как успешно выполнить вышеуказанный скрипт, сохранив существующий дизайн без изменений?

1 Ответ

2 голосов
/ 30 апреля 2020

Поскольку fetch принимает 3 аргумента (s, name, url), вам нужно передать 3 итераций в executor.map().

Когда вы сделаете это:

executor.map(fetch, *link_list)

link_list распаковывает около 49 кортежей, каждый из которых содержит 3 элемента (объект Session, имя пользователя и URL). Это не то, что вам нужно.

Что вам нужно сделать, так это сначала преобразовать link_list в 3 отдельных итераций (один для объектов Session, другой для имен пользователей и один для URL). Вместо того, чтобы делать это вручную, вы можете использовать zip() и оператор распаковки дважды, например, так:

            for result in executor.map(fetch, *zip(*link_list)):

Кроме того, когда я проверял ваш код, в get_links:

возникло исключение
    user_name = item.select_one(".user-details > a").get_text(strip=True)

AttributeError: 'NoneType' object has no attribute 'get_text'

item.select_one вернул None, который, очевидно, не имеет метода get_text(), поэтому я просто обернул его в блок try / Кроме того, поймал AttributeError и продолжил l oop.

Также обратите внимание, что класс Session в Requests не является поточно-ориентированным. К счастью , скрипт возвращал вменяемые ответы, когда я его запускал, но если вам нужен надежный скрипт, вам нужно решить эту проблему. Комментарий во 2-й ссылке показывает, как использовать один экземпляр Session для потока благодаря локальным данным потока. См .:

...