CoderCastrov logo
CoderCastrov
Парсер

Как парсить веб-сайт бесплатно менее чем за 5 минут (Github Actions)

Как парсить веб-сайт бесплатно менее чем за 5 минут (Github Actions)
просмотров
16 мин чтение
#Парсер

TLDR; Создайте новый репозиторий на Github и добавьте файл Workflow, содержащий schedule и оператор curl, который загружает JSON-файл с API-эндпоинта по расписанию cron в репозиторий. Визуализируйте результаты с помощью Flat-viewer. См. ниже более подробное пошаговое руководство.

on:
  push:
  workflow_dispatch:
  schedule:
    - cron:  '6,26,46 * * * *' # каждые двадцать минутjobs:
  scheduled:
    runs-on: ubuntu-latest
    steps:
    - name: Проверить этот репозиторий
      uses: actions/checkout@v2
    - name: Получить последние данные с API-эндпоинта
      run: |-
        curl -s "[https://www.nu.nl/block/lean_json/articlelist?limit=20&offset=0&source=latest&filter=site](https://www.nu.nl/block/lean_json/articlelist?limit=20&offset=0&filter=site)" | jq '.data.context.articles' > headlines.json
    - name: Зафиксировать и отправить, если данные изменились
      run: |-
        git config user.name "Автоматизировано"
        git config user.email "[actions@users.noreply.github.com](mailto:actions@users.noreply.github.com)"
        git add -A
        timestamp=$(date -u)
        git commit -m "Последние данные: ${timestamp}" || exit 0
        git push


Введение: Парсинг с помощью Git

Это руководство вдохновлено прекрасной концепцией "Парсинг с помощью Git" от Саймона Уиллисона https://simonwillison.net/2021/Mar/5/git-scraping/, которая сочетает бесплатные вычисления в Github Actions с хранением плоских файлов (например, json) в репозитории Git.

Github Actions

Для тех, кто не знаком с Github Actions: это позволяет запускать код в вашем репозитории Github на компьютерах Github бесплатно (которые на самом деле принадлежат Microsoft, так как они теперь владеют Github). Когда вы создаете учетную запись в Github, вы можете создать репозиторий и выбрать видимость. Пока вы устанавливаете видимость public, Microsoft предоставляет неограниченное количество минут для Github Action, что означает, что вы можете запускать запланированный парсинг веб-страниц бесплатно!

Использование GitHub Actions бесплатно как для публичных репозиториев, так и для собственных рабочих узлов.

Однако есть некоторые ограничения на общее время выполнения заданий в рамках рабочего процесса и самого рабочего процесса. Но это означает, что в теории мы можем запускать неограниченное количество парсеров веб-страниц в Github, пока мы сохраняем видимость репозитория Public (код и данные доступны в Интернете). Если вы хотите скрыть свой код, вы можете оставить репозиторий Private, но тогда вы ограничены определенным количеством минут вычислений в месяц (в настоящее время 2000 минут в месяц).

Плоские файлы в репозитории Git

В настоящее время многие веб-сайты используют JSON (что такое JSON?) в качестве формата данных по умолчанию. Преимущество заключается в том, что JSON (относительно) читаем и понятен для людей, поэтому мы можем сохранить его в файле, который мы сами можем прочитать: "плоский файл". Еще одно преимущество заключается в том, что многие другие программные приложения поддерживают JSON в качестве входных данных, например, приложение создает диаграммы на основе входных данных JSON. Теперь часто в современных системах данные хранятся в базе данных, а не в плоском файле. Но поскольку мы хотим создать простой парсер, который просто загружает некоторый JSON с URL-адреса и сохраняет его, плоский файл идеально подходит.

Git - популярная система контроля версий, которая отслеживает файлы со временем и является неотъемлемым инструментом для любого серьезного разработчика программного обеспечения. GitHub - это провайдер управляемых репозиториев Git (которые являются отслеживаемыми проектами). Обычно мы используем репозиторий в основном для хранения версий кода в программном проекте, но мы также можем использовать его для хранения разных версий плоского файла, который мы парсим. Таким образом, каждый раз, когда мы парсим новые данные, мы создаем новую версию в истории, сохраняя при этом копию (коммит) предыдущей версии. Таким образом, легко просмотреть изменения между версиями, что обычно не так просто в традиционных базах данных.

Github Action Парсинг Git

Это позволяет выполнять любое количество действий, которые ранее выполнялись на вашем собственном компьютере, в облаке с использованием Github Actions. Хорошим примером использования является парсинг веб-страниц. И поскольку мы часто можем получать данные из API в формате JSON и сохранять этот файл в репозитории Github, это создает очень мощную возможность для практически бесплатного, запланированного и отслеживаемого парсинга веб-страниц. В этом руководстве я покажу вам, как я парсю новостные статьи с голландского новостного сайта https://www.nu.nl по расписанию и сохраняю файл JSON с содержимым статей в репозиторий https://github.com/lassebenni/git-scraper-nu-nl.


Шаги для создания парсера

1. Найти источник данных для парсинга

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

Мы перейдем на https://www.nu.nl, популярный нидерландский новостной веб-сайт. Это мой основной сайт в интернете уже насколько я помню. Он предлагает отрывки новостей и посещается миллионами людей каждый месяц.

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

2. Изучение сетевых запросов

Когда вы получаете доступ к веб-сайту с помощью его URL, ваш браузер (что такое браузер?) фактически выполняет множество HTTP-запросов к различным URL-адресам, чтобы получить все данные, необходимые для отображения на экране. Некоторые из этих запросов возвращают изображения и текст на странице, а другие могут извлекать рекламу или другие связанные действия, которые вы можете не видеть на самой странице. Для более подробного объяснения протокола HTTP.

Современные веб-сайты часто разделяют данные (например, новости на новостном сайте) от визуальных частей страницы: HTML (что такое HTML?). Это означает, что браузер сначала загружает макет страницы, затем выполняет запрос данных и вставляет эти данные в макет. Одна из причин этого заключается в том, что разные команды могут предоставлять данные (новостные статьи), в отличие от команды, поддерживающей веб-сайт. Это "разделение ответственности" значительно облегчает переход на новый веб-сайт или систему источника данных по сравнению с одним приложением, ответственным как за веб-сайт, так и за отображаемые данные. Это разделение - находка для парсеров, потому что браузеры могут показывать вам эти отдельные запросы в деталях и получать только данные, не просеивая HTML. Как только мы найдем конкретный URL, содержащий нужные нам данные, мы можем просто продолжать их парсить.

ПРИМЕЧАНИЕ: Не все веб-сайты будут возвращать данные в виде отдельных HTTP-запросов, это довольно новое развитие. Обычно старые веб-сайты возвращают данные, которые вы ищете, встроенные в HTML. В этом случае мы не можем использовать здесь описанный метод, но вам придется парсить HTML с помощью чего-то вроде BeautifulSoup.

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

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

«Инспектировать» веб-сайт

Это приведет нас к вкладке «Инспектор», которая показывает HTML, составляющий страницу. Нас это не интересует, поэтому выберите вкладку Сеть.

Сетевые запросы

Здесь показаны все сетевые HTTP-запросы, которые браузер выполнил от имени веб-сайта. Их много, и неясно, какой из них содержит новостные статьи, если вообще содержит.

3. Найти конечную точку API

Веб-сайты (и другие программные приложения) общаются друг с другом с помощью API (что такое API?). Они имеют разные формы, но в отношении веб-сайтов они часто представлены в виде URL-адресов. Как мы можем перейти по адресу https://www.nu.nl и получить HTML-код веб-сайта, также существует бесчисленное количество других URL-адресов, которые возвращают какие-то данные, будь то HTML, JSON, XML или любой другой формат. Как я уже сказал, часто сейчас данные отделяются от самого веб-сайта. Это означает, что веб-сайт будет указывать браузеру вызвать конечную точку API (другими словами, конкретный URL-адрес), чтобы получить данные, которые затем будут отображаться на веб-сайте. Мы хотим выяснить, является ли это так в случае NU.nl.

Мы можем пройти через каждый запрос и посмотреть, возвращает ли он что-то интересное. Это может занять очень много времени, так как не всегда ясно, какой тип данных возвращает запрос, если вообще возвращает. К счастью, мы можем ускорить процесс, используя фильтр. Сначала выберите фильтр "XHR" в верхней части. Это отфильтрует все, что не является «XMLHttpRequest». Запросы XHR предназначены для динамического выполнения скриптов и получения данных. Не беспокойтесь, если вам кажется, что это слишком сложно для понимания, на данный момент это просто означает, что мы фильтруем запросы, которые для нас неинтересны. Надеюсь, останется один запрос с заголовками.

“Network”

Затем используйте функцию "поиск" (нажмите на значок лупы), в которую можно ввести поисковый термин. В этом случае я буду искать один из заголовков на главной странице: "Wiegman leidt Engeland". Это дает один результат. Бинго.

Поиск запросов

Нажатие на строку под результатом покажет информацию о HTTP-запросе справа.

JSON результат

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

Заголовки запроса

Здесь скопируйте URL из раздела "GET" вверху: https://www.nu.nl/block/lean_json/articlelist?limit=20&offset=0&source=latest&filter=site. Если мы введем этот URL в нашем браузере, мы получим большой блок JSON. Просмотрев его, мы можем увидеть, что он содержит заголовки:

“Prettified” JSON response

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

Сырой JSON ответ

В любом случае, просматривая JSON, мы видим, что он содержит то, что нам нужно: заголовки статей вместе с датами создания и обновления. Он также содержит некоторую дополнительную "мета" информацию, которая нас не интересует, например, информацию о "компоненте". Нам все равно придется сделать небольшую настройку позже, чтобы извлечь только сами статьи из JSON, но пока мы должны быть рады, что нашли "конечную точку API". Это то, что мы называем URL, который содержит данные, где API означает Application Programming Interface, что является более старым термином для части программной системы, которая используется для общения с внешним миром/другими системами. Если мы продолжим посещать URL и сохранять результаты, мы сможем начать создавать историю заголовков. Давайте перейдем к самой части парсинга сейчас!

4. Создайте репозиторий на Github (бесплатно)

Теперь, когда у нас есть конечная точка, мы можем приступить к части с Github. Если у вас еще нет аккаунта, перейдите на github.com и создайте его (это бесплатно). Затем создайте новый репозиторий: https://github.com/new

New Github repository

Убедитесь, что выбрали описательное имя, например "парсер-git-заголовков-новостей", (это будет видно публично в интернете), установите флажок "Добавить файл README" и оставьте остальные настройки без изменений.

Важно оставить его публичным, чтобы парсить бесплатно!

Создайте репозиторий:

Create repository

5. Добавьте файл Workflow

Теперь, когда репозиторий создан, нам нужно создать файл Workflow. Это просто файл, который содержит шаги, которые должны выполняться Github Actions во время каждого парсинга. Формат файла должен быть yaml. Нажмите Add File

Add File

Теперь введите путь к файлу, который должен быть .github/workflows/scrape.yml. Обратите внимание, что он должен находиться в папке ".github/workflows", но его можно назвать как угодно, например my_scraper.yml

Workflow file

Теперь самое главное: мы скопируем оригинальный Workflow Саймона Уиллисона (https://github.com/simonw/ca-fires-history/blob/main/.github/workflows/scrape.yml), но немного измененный под наши нужды:

on:
  push:
  workflow_dispatch:
  schedule:
    - cron:  '6,26,46 * * * *' # каждые двадцать минут
jobs:
  scheduled:
    runs-on: ubuntu-latest
    steps:
    - name: Check out this repo
      uses: actions/checkout@v2
    - name: Fetch latest data from the API endpoint
      run: |-
        curl -s "[https://www.nu.nl/block/lean_json/articlelist?limit=20&offset=0&source=latest&filter=site](https://www.nu.nl/block/lean_json/articlelist?limit=20&offset=0&filter=site)" | jq '.data.context.articles' > headlines.json
    - name: Commit and push if the data has changed
      run: |-
        git config user.name "Automated"
        git config user.email "[actions@users.noreply.github.com](mailto:actions@users.noreply.github.com)"
        git add -A
        timestamp=$(date -u)
        git commit -m "Latest data: ${timestamp}" || exit 0
        git push

Как вы можете видеть, файл соответствует формату YAML. Это означает, что строки имеют отступы и используют структуру ключ/значение. Это самый сложный шаг в руководстве, поэтому я разберу его по шагам:

on:
  push:
  workflow_dispatch:
  schedule:
    - cron:  '6,26,46 * * * *' # каждые двадцать минут

Это означает, что Workflow будет выполняться "при" каждом из следующих трех условий:

Следующий шаг определяет "job", который мы фактически выполняем в Workflow:

jobs:
  scheduled:
    runs-on: ubuntu-latest

Мы называем наш job "scheduled", но вы можете выбрать любое имя. Единственный другой параметр, который мы выбираем здесь, это параметр "runs-on", который определяет тип виртуальной машины, на которой будет выполняться вычисление Github Action. Вы можете увидеть различные типы вычислений здесь. Это важно, потому что нам нужна Linux-машина с определенными предустановленными программами, такими как curl и jq, которые мы собираемся использовать в следующих шагах.

steps:
    - name: Check out this repo
      uses: actions/checkout@v2

Это отдельные шаги, из которых состоит Workflow Job. Они выполняются сверху вниз, начиная с верхнего шага, называемого "Check out this repo". Имя может быть любым, а параметр uses указывает на Github Action, доступное в Marketplace. Это общедоступный рынок, где разработчики могут создавать и делиться своими собственными действиями. Github также предоставляет некоторые свои собственные действия. "actions/checkout@v2" - это очень простое действие, которое загружает репозиторий на виртуальную машину, на которой выполняется Workflow, чтобы код можно было использовать.

- name: Fetch latest data from the API endpoint
      run: |-
        curl -s "[https://www.nu.nl/block/lean_json/articlelist?limit=20&offset=0&source=latest&filter=site](https://www.nu.nl/block/lean_json/articlelist?limit=20&offset=0&filter=site)" | jq '.data.context.articles' > headlines.json

Второе действие самое интересное: оно выполняет два действия. Первое - выполнить HTTP-запрос к API Endpoint, который мы нашли при исследовании сетевых запросов на предыдущем шаге. Curl - это программа для Linux, которая может выполнять запросы, как и ваш браузер. Мы можем заставить его загрузить JSON-ответ в файл (curl "<some-url>"). Этого было бы достаточно, но мы можем сделать немного лучше, явно извлекая статьи из JSON. Если вы помните JSON-ответ, мы получили больше данных, чем только статьи:

Примечание: очень важно окружить URL двойными кавычками (""), когда используется curl. В противном случае символ "&" в https://www.nu.nl/block/lean_json/articlelist?limit=20&offset=0&source=latest&filter=site нарушит функциональность curl и не сделает то, что мы хотим.

JSON response

JSON разделен на "словарь" объектов ключ-значение. Они могут быть вложенными, то есть ключ может содержать значение, которое содержит другой ключ и так далее. В данном случае ключ "data" имеет объект с несколькими значениями: component и context. Затем context содержит собственные значения: limit, offset, source и т.д. Нас интересует только ключ articles, который содержит список объектов статей. Используя программу [jq](https://stedolan.github.io/jq/)jq, мы можем фильтровать правильные данные внутри json, игнорируя остальное.

curl -s "[https://www.nu.nl/block/lean_json/articlelist?limit=20&offset=0&source=latest&filter=site](https://www.nu.nl/block/lean_json/articlelist?limit=20&offset=0&filter=site)" **| jq '.data.context.articles'** > headlines.json

Используя |(pipe) между curl и jq, мы отправляем вывод из curl в качестве ввода в jq. Это очень удобно и позволяет нам избежать создания файлов в качестве промежуточных. Это называется "пайпингом" и является основой работы с программами в Unix/Linux. Команда jq затем принимает опцию '.data.context.article', которая следует за вложенными ключами в JSON-ответе от "data" -> "context" -> "article" и возвращает только все, что находится под ключом "article". Теперь у нас есть только JSON-список статей, который поможет визуализации с использованием Flat-viewer.

Последняя часть

curl -s "[https://www.nu.nl/block/lean_json/articlelist?limit=20&offset=0&source=latest&filter=site](https://www.nu.nl/block/lean_json/articlelist?limit=20&offset=0&filter=site)" **| jq '.data.context.articles'** **> headlines.json**

сохраняет результаты операций curl и jq (отфильтрованный JSON) в файл с именем headlines.json с помощью оператора "перенаправления" (>). Мы могли бы назвать этот файл как угодно, но для простоты мы называем его headlines.json.

Наконец, последний шаг выполнит действия Git, необходимые для сохранения результата. Сначала он устанавливает имя и электронную почту для коммита (вы можете изменить это, если хотите). Затем он выполняет операции add, commit, push, чтобы сохранить новые изменения в файле. Обратите внимание, что если с момента последнего парсинга не было никаких изменений, не будет создан новый коммит. Это гарантирует чистую историю. Всякий раз, когда мы делаем коммит, предыдущая версия файла все равно остается в репозитории и всегда может быть восстановлена.

Зафиксируйте файл Workflow:

Commit the Workflow

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

6. Проверьте действие

Перейдите на вкладку "Действия" в репозитории.

Actions tab

Теперь должен отображаться первый запуск рабочего процесса:

First action

Нажмите на действие:

Successful run

Нажмите на запуск "Scheduled":

Scheduled action steps

Каждый раз, когда выполняется действие, мы можем видеть результаты на вкладке "Действия". Для нашего первого запуска должны отображаться выполненные шаги в рабочем процессе. Сначала он получил данные, затем сохранил результаты в репозитории. Отлично! Давайте проверим, можем ли мы теперь увидеть файл! Вернитесь на вкладку "Код" в пользовательском интерфейсе.

The file is there

Мы видим, что файл с названием headlines.json был добавлен. Волнительное время! Давайте откроем его.

headlines.json

Файл содержит JSON для статей и больше не содержит полных метаданных, как в исходном ответе. Отлично! У меня есть еще один последний трюк в запасе, прежде чем мы закончим.

7. Просмотр результатов с помощью Flat-viewer

В 2021 году Github представил приложение flat-viewer для изучения плоских файлов (CSV и JSON) в рамках своей инициативы Flat Data. Оно интегрируется с репозиторием и может показывать изменения между коммитами (версиями) файла. У него есть некоторые возможности фильтрации и в целом оно очень удобно в использовании.

Flat-viewer

Очень круто то, что оно уже активно для всех публичных репозиториев на Github! Если вы добавите "flat" перед URL-адресом Github, содержащим плоский файл, он загрузит данные в приложение flat-viewer! Давайте проверим это с нашими данными: https://flatgithub.com/lassebenni/git-scraper-nu-nl/blob/main/headlines.json

Flat-viewer наших заголовков

Мы можем изучать наши данные в формате JSON и даже фильтровать их, все в браузере! Вы даже можете изучить различные коммиты (версии) файла в верхнем меню, которое визуально отображает изменения между файлами. Как круто это?! Однако есть некоторые недостатки: фильтрация довольно ограничена, она не может обрабатывать вложенные объекты (такие как объекты в списках) и иногда неправильно обрабатывает даты. Но все же, это очень хороший способ быстро проверить и изучить данные, которые мы парсим.

git-scraper-nu-nl

Парсер новостных статей с сайта nu.nlПосмотреть результаты

9. Добавление тега к репозиторию

В качестве последнего шага давайте добавим тег к нашему репозиторию, чтобы другие пользователи могли его найти. Нажмите на "Зубчатое колесо" на странице репозитория:

Settings

Затем добавьте тему git-scraping и сохраните.

git-scraping Topic

Мы видим, что репозиторий теперь имеет правильную тему.

git-scraping topic

Это означает, что его можно найти среди других проектов git-scraping на Github.

git-scraping projects

Возможно, вы можете исследовать и найти интересные проекты, или другие пользователи могут учиться на вашем коде!

Заключение

Вдохновленный Саймоном Уиллисоном, я показал вам, как создать простой, но мощный планировщик парсера бесплатно, объединив Github Actions и добротную работу веб-детектива. Теперь имейте в виду, что это очень простой парсер, так как он не включает в себя никакой формы аутентификации, куки или пагинации (парсинг нескольких страниц) или преобразования результатов. Для более сложного парсинга я бы рекомендовал написать парсер на языке программирования, таком как Python или JavaScript. Дайте мне знать, понравился ли вам этот руководство и заинтересованы ли вы в более продвинутом/настраиваемом проекте парсинга, где мы будем развивать шаги из этого руководства. Также, пожалуйста, дайте мне знать, если вы создадите свой собственный парсер и оставьте ссылку в комментариях! Спасибо и продолжайте парсить!