CoderCastrov logo
CoderCastrov
Парсер

Как парсить новости с Reuters с помощью Python и Scrapy

Как парсить новости с Reuters с помощью Python и Scrapy
просмотров
9 мин чтение
#Парсер

Пошаговое руководство по сбору новостных данных

Добро пожаловать в это практическое руководство по извлечению новостных статей с веб-сайта Reuters с использованием Python и Scrapy.

В эпоху принятия решений на основе данных, возможность собирать новостные данные может дать нам много преимуществ, начиная от понимания актуальных тем до проведения анализа настроений и улучшения журналистики данных. В течение этой статьи мы разблокируем потенциал парсинга веб-страниц, преобразуя сырые данные в осмысленные идеи.

Будь вы опытным программистом или новичком в программировании, это пошаговое руководство предназначено, чтобы оснастить вас навыками, необходимыми для ориентации в мире парсинга веб-страниц. Давайте приступим!

Введение в Scrapy

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

В основе Scrapy лежит библиотека Python, специально разработанная для парсинга веб-страниц, метода, нацеленного на быстрое и эффективное извлечение больших объемов данных с веб-сайтов.

Этот инструмент открывает мир возможностей для анализа данных, прогнозирования трендов и многого другого. Давайте поглубже изучим его функциональные возможности и то, как его можно использовать для раскрытия истинной силы данных.

Сначала несколько понятий:

С помощью этих компонентов Scrapy способен справиться с проектами парсинга веб-страниц большого масштаба. Его способность обрабатывать как ширину (несколько веб-сайтов), так и глубину (детальное извлечение данных) задач парсинга веб-страниц делает его лучшим выбором для глобальных дата-майнеров. И сегодня мы воспользуемся этой силой, чтобы изучить кладовую, которой является Reuters. Давайте начнем!

Scraping Process for Reuters

Цели парсинга

В нашем путешествии по сбору данных, мы обратим внимание на настоящую золотую жилу информации - веб-сайт Reuters. Эта статья легко может быть адаптирована для других новостных веб-сайтов, которые функционируют похожим образом, таких как CNN или New York Times, но мы будем использовать Reuters для этой статьи. В частности, нас интересует парсинг двух типов страниц с веб-сайта: результатов поиска и отдельных статей.

Парсинг результатов поиска

Наш первый парсер, определенный в файле reuters.py, будет описывать, как извлекать информацию из результатов поиска Reuters. Учитывая конкретный поисковый запрос - те же слова, которые вы бы использовали при поиске на их веб-сайте - парсер создает URL для поиска и получает список статей с их заголовками, датами и темами. При запуске с помощью Scrapy он выведет CSV-файл со списком этих статей, и этот список будет использоваться для последующего парсинга каждой статьи по отдельности.

Результаты поиска Reuters по запросу «Война в Украине»

Парсинг конкретных статей

Наш второй парсер, определенный в reuters_article.py, более специализированный, создан для погружения в глубины отдельных статей и извлечения конкретных данных, таких как заголовок статьи, дата, автор и текст из всех абзацев.

Reuters Article About The Ukraine War

С помощью совместных усилий этих двух парсеров мы готовы и готовы добывать сокровища Reuters, находя каждый крупицу информации, которую мы можем найти. Первый парсер получает все соответствующие статьи для поискового запроса, а второй парсит эти статьи одну за другой. Так что держитесь крепче, пришло время начать копать!

Настройка наших инструментов

Мы начнем наше путешествие с небольшого исследования веб-сайта Reuters. Мы будем искать элементы, которые нас интересуют, и сохранять их в конфигурационных файлах JSON под названиями selectors_reuters.json и selectors_reuters_article.json. Затем эти JSON-файлы будут загружены в наши парсеры для извлечения соответствующей информации.

Вот краткий обзор selectors_reuters.json:

{
  "titles": "a[data-testid=\"Heading\"]::text",
  "urls": "a[data-testid=\"Heading\"]::attr(href)",
  "dates": "time[data-testid=\"Body\"]::attr(datetime)",
  "topics": "span[data-testid=\"Text\"]::text"
}

И selectors_reuters_article.json:

{
  "title": "h1[data-testid=\"Heading\"]::text",
  "date": "time > span:nth-child(2)::text",
  "author": "div[class*=\"article-header__author\"]::text",
  "paragraphs": "p[data-testid*=\"paragraph-\"]::text"
}

Внутри этих JSON-файлов у нас есть ряд CSS-селекторов. Они нацеливаются на конкретные HTML-элементы на веб-странице, которые содержат ценные данные, которые мы ищем. Взгляните на картинку ниже для более наглядного примера.

Пример CSS-селектора для элемента «input» с классом «form-control»

Каждый CSS-селектор захватывает определенный HTML-элемент и связанные с ним данные. Например, на изображении у нас input.form-control - это селектор для каждого тега input, имеющего класс form-control.

В наших JSON-файлах еще один пример - "a[data-testid=\"Heading\"]::text", который нацеливает текст внутри тегов якоря <a>, имеющих атрибут data-testid со значением "Heading".

Я не буду углубляться в то, как использовать CSS-селекторы здесь, но существует много ресурсов, которые очень хорошо объясняют их, поэтому, если вам интересно, ознакомьтесь со статьей ниже.

CSS Селекторы - Памятка по классам, именам и списку дочерних селекторов

CSS селекторы позволяют выбирать и стилизовать HTML элементы. В частности, CSS селекторы позволяют выбирать...

www.freecodecamp.org

Теперь давайте взглянем на код, чтобы увидеть, как эти селекторы работают с нашими парсерами для сбора данных!

Шаг 1: Определение Парсера и Загрузка Селекторов

Давайте начнем с определения структуры скелета Scrapy парсеров и чтения определенной выше конфигурации. Структура файлов нашего проекта будет выглядеть следующим образом:

Структура файлов для парсинга новостей

Папка config содержит нашу конфигурацию селекторов, папка spiders содержит различные парсеры, которые мы собираемся определить через минуту, а остальные файлы содержат вспомогательные функции (postprocessing.py и preprocessing.py) или специфичные для Scrapy настройки.py, где содержатся все настройки проекта, такие как активация конвейеров, промежуточных обработчиков и т.д.

При первом запуске работы с парсером Scrapy, нам по крайней мере нужно определить функции start_requests и parse - первая для определения URL-адресов, которые будут парситься, и вторая для определения информации, которую нужно извлечь после получения ответа.

В обоих Python парсерах мы открываем соответствующий JSON файл и используем json.load() для преобразования CSS селекторов в словарь Python. Тогда наш скелет поискового парсера будет выглядеть так:

class ReutersSpider(scrapy.Spider):
    """    Парсер, который обходит список результатов поиска статей Reuters.    """
    name = 'reuters'
    with open("config/selectors_reuters.json") as selector_file:
        selectors = json.load(selector_file)
    
    def start_requests(self):
        pass

    def parse(self, response: TextResponse, **kwargs):
        pass

А скелет парсера статьи будет выглядеть так (обратите внимание на list_of_articles, который указывает на файл вывода поискового парсера).

class ReutersArticleSpider(scrapy.Spider):
    """    Парсер, который обходит список статей по отдельности, основанный на парсере поиска Reuters. Для отображения страниц продуктов не требуется JS.    """
    name = 'reuters_article'
    list_of_articles = "output/reuters.csv"
    with open("config/selectors_reuters_article.json") as selector_file:
        selectors = json.load(selector_file)

    def start_requests(self):
        pass

    def parse(self, response: TextResponse, **kwargs):
        pass

Теперь наши парсеры и селекторы готовы к действию, нам просто нужно определить логику парсинга.

Шаг 2: Создание запросов

Метод start_requests в каждом парсере определяет начальную точку нашего поиска сокровищ. Это обязательная функция для создания списка start_urls, которые Scrapy использует в качестве целей парсинга. Сначала нам нужно определить несколько вспомогательных функций для наших парсеров. Одна для создания URL-адреса поиска, одна для создания URL-адреса прокси ScrapeOps, одна для получения всех URL-адресов из списка словарей и одна для чтения списка словарей из CSV-файла, полученного от парсера поиска.

Небольшое замечание - вы можете пропустить использование прокси ScrapeOps, хотя у них есть щедрая бесплатная версия, если вы хотите столкнуться с проблемами парсинга самостоятельно, но имейте в виду, что вы можете столкнуться с проблемами, которые не описаны в этой статье.

def create_reuters_search_url(query):
    return f"https://www.reuters.com/site-search/?query={query.replace(' ', '+')}"

def create_scrapeops_url(url, js=False, wait=False):
    key = os.getenv("SCRAPEOPS_API_KEY")
    scraping_url = f"https://proxy.scrapeops.io/v1/?api_key={key}&url={url}"
    if js:
        scraping_url += "&render_js=true"
    if wait:
        scraping_url += f"&wait_for={wait}"
    return scraping_url

def get_urls_from_dict(list_of_dicts):
    urls = []
    for dict_with_url in list_of_dicts:
        if dict_with_url.get("url"):
            urls.append(dict_with_url.get("url"))
    return urls

def read_csv(csv_path: str):
    items = []
    with open(csv_path) as csvfile:
        reader = csv.DictReader(csvfile, delimiter=',')
        for row in reader:
            items.append(row)
    return items

В парсере поиска, определенном в reuters.py, мы создаем URL-адрес поиска с определенным запросом, используя первые две вспомогательные функции, которые мы определили выше. Здесь мы хотим искать ключевые слова "украинская война":

def start_requests(self):
    start_urls = [
        create_scrapeops_url(create_reuters_search_url("украинская война"), wait="time")
    ]
    for url in start_urls:
        yield scrapy.Request(url=url, callback=self.parse)

В reuters_article.py мы получаем все URL-адреса из CSV-файла, полученного от парсера поиска, и поочередно обрабатываем статьи, используя первые две вспомогательные функции, которые мы определили выше.

def start_requests(self):
    start_urls = get_urls_from_dict(read_csv(self.path_for_urls))
    for url in start_urls:
        yield scrapy.Request(url=create_scrapeops_url(url), callback=self.parse)

Шаг 3: Парсинг ответа

Теперь мы достигли пункта назначения и пришло время найти сокровище. Наши пауки используют селекторы для извлечения данных из HTML-ответа. В частности, метод parse будет использовать CSS-селекторы, которые мы загрузили из нашего конфигурационного файла, чтобы по одному извлекать важные элементы из HTML-ответа.

В поисковом пауке, определенном в reuters.py, мы будем извлекать заголовки, URL-адреса, даты и темы статей с помощью CSS-селекторов и создавать JSON-список элементов статей, содержащих эти данные:

def parse(self, response: TextResponse, **kwargs):
    titles = [clean(title, remove_special=False) for title in response.css(self.selectors["titles"]).getall()]
    urls = ["https://www.reuters.com" + url for url in response.css(self.selectors["urls"]).getall()]
    dates = [clean(description) for description in response.css(self.selectors["dates"]).getall()]
    topics = [clean(metadata) for metadata in response.css(self.selectors["topics"]).getall()]
    
    articles = []
    for title, url, date, topic in zip(titles, urls, dates, topics):
        article = {
            "title": title,
            "url": url,
            "date": date,
            "topic": topic,
        }
        articles.append(article)
    
    return articles

В специфическом пауке для статей, определенном в reuters_article.py, мы будем извлекать для каждой отдельной статьи ее заголовок, текст, дату публикации и автора, и создавать JSON-элемент, содержащий эти данные:

def parse(self, response: TextResponse, **kwargs):
    title = clean(response.css(self.selectors["title"]).get(), remove_special=False)
    paragraphs = response.css(self.selectors["paragraphs"]).getall()
    text = " ".join([clean(paragraph) for paragraph in paragraphs])
    date = clean(response.css(self.selectors["date"]).get())
    author = clean(response.css(self.selectors["author"]).get())
    url = get_source_url_from_scraping_url(response.request.url)

    return {
        "title": title,
        "url": url,
        "date": date,
        "author": author,
        "text": text,
    }

Шаг 4: Запуск пауков

Когда наши пауки закодированы и готовы исследовать просторы веба, пришло время выпустить их на свободу. В этом разделе мы узнаем, как сделать это с помощью командной строки Scrapy (CLI).

Для запуска наших пауков мы будем использовать следующую команду:

scrapy crawl reuters -o output/reuters.csv & scrapy crawl reuters_article -o output/reuters_article.csv

Вот краткое описание того, что происходит в этой команде:

И вуаля! С помощью простой команды мы запустили двух умных веб-пауков в их экспедицию по сбору данных. Пока они перемещаются по вебу, они будут собирать ценные данные и аккуратно организовывать их в CSV-файлах, готовых для анализа. Счастливого парсинга!

Заключение

И вот мы подошли к концу - пошаговое руководство по созданию собственной экспедиции по парсингу веб-страниц с помощью Scrapy для извлечения данных с сайта Reuters. Мы изучили мощь Scrapy и CSS-селекторов, а затем отправили пауков в дебри веба. Невероятно, что можно достичь с помощью сочетания Python и библиотеки Scrapy в сборе и обработке данных.

Но наше приключение не заканчивается здесь. Истинная сила парсинга веб-страниц заключается в его универсальности и масштабируемости. С вашим новым пониманием вы можете настроить своих пауков для исследования других веб-сайтов, изучения различных типов статей или поиска другой информации.

Так что оснаститесь этими новыми навыками и продолжайте исследовать богатые, неизведанные природные запасы веб-данных.

Но помните, как и в любом приключении, всегда будьте уважительны к местным правилам (в данном случае файлу robots.txt веб-сайта). Парсите только те данные, к которым у вас есть разрешение на доступ. Счастливой охоты за данными!