Рубрики
Без рубрики

Как сайты Scrape с Python 3

Автор оригинала: André Jaenisch.

Веб-соскреб – это процесс извлечения данных с веб-сайтов.

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

Массивный соскоб может поставить сервер под болью напряжением, которое может привести к отрицанию сервиса. И ты этого не хочешь.

Кто должен прочитать это?

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

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

Мы запустим код на локальной машине, чтобы исследовать некоторые сайты. С некоторыми настройками вы могли бы сделать его и на сервере.

Что вы узнаете в этой статье

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

Эта статья также объяснит, что делать, если этот веб-сайт использует JavaScript для визуализации контента (например, rance.js или ingular).

Предпосылки

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

  • BeautifulSoup4 (версия 4.9.0 во время написания)
  • Запросы (версия 2.23.0 во время написания)
  • WordCloud (версия 1.17.0 во время написания, необязательно)
  • Селен (версия 3.141.0 во время написания, необязательно)

Вы можете найти код для этого проекта в этом Git Repository на Github Отказ

Для этого примера мы собираемся соскребать Основной закон для Федеративной Республики Германия Отказ (Не волнуйтесь, я проверил свои условия обслуживания. Они предлагают XML-версию для обработки машины, но эта страница служит примером обработки HTML. Так что должно быть в порядке.)

Шаг 1: Загрузите источник

Первые вещи первыми: я создаю файл URLS.TXT Удерживая все URL-адреса, которые я хочу скачать:

https://www.gesetze-im-internet.de/gg/art_1.html
https://www.gesetze-im-internet.de/gg/art_2.html
https://www.gesetze-im-internet.de/gg/art_3.html
https://www.gesetze-im-internet.de/gg/art_4.html
https://www.gesetze-im-internet.de/gg/art_5.html
https://www.gesetze-im-internet.de/gg/art_6.html
https://www.gesetze-im-internet.de/gg/art_7.html
https://www.gesetze-im-internet.de/gg/art_8.html
https://www.gesetze-im-internet.de/gg/art_9.html
https://www.gesetze-im-internet.de/gg/art_10.html
https://www.gesetze-im-internet.de/gg/art_11.html
https://www.gesetze-im-internet.de/gg/art_12.html
https://www.gesetze-im-internet.de/gg/art_12a.html
https://www.gesetze-im-internet.de/gg/art_13.html
https://www.gesetze-im-internet.de/gg/art_14.html
https://www.gesetze-im-internet.de/gg/art_15.html
https://www.gesetze-im-internet.de/gg/art_16.html
https://www.gesetze-im-internet.de/gg/art_16a.html
https://www.gesetze-im-internet.de/gg/art_17.html
https://www.gesetze-im-internet.de/gg/art_17a.html
https://www.gesetze-im-internet.de/gg/art_18.html
https://www.gesetze-im-internet.de/gg/art_19.html

Далее я пишу немного кода Python в файле под названием Scraper.py Для загрузки HTML из этого файлов.

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

from os import path
from pathlib import PurePath

import requests

with open('urls.txt', 'r') as fh:
    urls = fh.readlines()
urls = [url.strip() for url in urls]  # strip `\n`

for url in urls:
    file_name = PurePath(url).name
    file_path = path.join('.', file_name)
    text = ''

    try:
        response = requests.get(url)
        if response.ok:
            text = response.text
    except requests.exceptions.ConnectionError as exc:
        print(exc)
    
    with open(file_path, 'w') as fh:
        fh.write(text)

    print('Written to', file_path)

Загрузив файлы, я могу обработать их локально столько, сколько я хочу, не будучи зависимым от сервера. Постарайтесь быть хорошим гражданином, хорошо?

Шаг 2: анализ источника

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

В моем случае я подумал, что хочу текст закона без какой-либо разметки. Обогащая элемент у него есть идентификатор Контейнер Отказ Используя BeautifulSoup, я вижу, что комбинация Найти и get_text сделаю то, что хочу.

Так как у меня сейчас есть второй шаг, я немного решакторую код, надевая его в функции и добавляю минимальную CLI.

from os import path
from pathlib import PurePath
import sys

from bs4 import BeautifulSoup
import requests


def download_urls(urls, dir):
    paths = []

    for url in urls:
        file_name = PurePath(url).name
        file_path = path.join(dir, file_name)
        text = ''

        try:
            response = requests.get(url)
            if response.ok:
                text = response.text
            else:
                print('Bad response for', url, response.status_code)
        except requests.exceptions.ConnectionError as exc:
            print(exc)
    
        with open(file_path, 'w') as fh:
            fh.write(text)

        paths.append(file_path)

    return paths

def parse_html(path):
    with open(path, 'r') as fh:
        content = fh.read()

    return BeautifulSoup(content, 'html.parser')

def download(urls):
    return download_urls(urls, '.')

def extract(path):
    return parse_html(path)

def transform(soup):
    container = soup.find(id='container')
    if container is not None:
        return container.get_text()

def load(key, value):
    d = {}
    d[key] = value
    return d

def run_single(path):
    soup = extract(path)
    content = transform(soup)
    unserialised = load(path, content.strip() if content is not None else '')
    return unserialised

def run_everything():
    l = []

    with open('urls.txt', 'r') as fh:
        urls = fh.readlines()
    urls = [url.strip() for url in urls]

    paths = download(urls)
    for path in paths:
        print('Written to', path)
        l.append(run_single(path))

    print(l)

if __name__ == "__main__":
    args = sys.argv

    if len(args) is 1:
      run_everything()
    else:
        if args[1] == 'download':
            download([args[2]])
            print('Done')
        if args[1] == 'parse':
            path = args[2]
            result = run_single(path)
            print(result)

Теперь я могу запустить код тремя способами:

  1. Без каких-либо аргументов запускать все (то есть загрузить все URL-адреса и извлечь их, а затем сохранить на диск) через: Python Scraper.py.
  2. С аргументом Скачать и URL для скачивания Python Scraper.py Скачать https://www.gesetze-im-internet.de/gg/art_1.html Отказ Это не будет обработать файл.
  3. С аргументом Разбор И FilePath для Parse: python scraper.py art_1.html Отказ Это будет пропустить шаг загрузки.

С этим есть последняя вещь отсутствует.

Шаг 3: Формат источника для дальнейшей обработки

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

from os import path
from pathlib import Path, PurePath
import sys

from bs4 import BeautifulSoup
import requests
from wordcloud import WordCloud

STOPWORDS_ADDENDUM = [
    'Das',
    'Der',
    'Die',
    'Diese',
    'Eine',
    'In',
    'InhaltsverzeichnisGrundgesetz',
    'im',
    'Jede',
    'Jeder',
    'Kein',
    'Sie',
    'Soweit',
    'Über'
]
STOPWORDS_FILE_PATH = 'stopwords.txt'
STOPWORDS_URL = 'https://raw.githubusercontent.com/stopwords-iso/stopwords-de/master/stopwords-de.txt'


def download_urls(urls, dir):
    paths = []

    for url in urls:
        file_name = PurePath(url).name
        file_path = path.join(dir, file_name)
        text = ''

        try:
            response = requests.get(url)
            if response.ok:
                text = response.text
            else:
                print('Bad response for', url, response.status_code)
        except requests.exceptions.ConnectionError as exc:
            print(exc)
    
        with open(file_path, 'w') as fh:
            fh.write(text)

        paths.append(file_path)

    return paths

def parse_html(path):
    with open(path, 'r') as fh:
        content = fh.read()

    return BeautifulSoup(content, 'html.parser')

def download_stopwords():
    stopwords = ''

    try:
        response = requests.get(STOPWORDS_URL)
        if response.ok:
            stopwords = response.text
        else:
            print('Bad response for', url, response.status_code)
    except requests.exceptions.ConnectionError as exc:
        print(exc)

    with open(STOPWORDS_FILE_PATH, 'w') as fh:
        fh.write(stopwords)

    return stopwords

def download(urls):
    return download_urls(urls, '.')

def extract(path):
    return parse_html(path)

def transform(soup):
    container = soup.find(id='container')
    if container is not None:
        return container.get_text()

def load(filename, text):
    if Path(STOPWORDS_FILE_PATH).exists():
        with open(STOPWORDS_FILE_PATH, 'r') as fh:
            stopwords = fh.readlines()
    else:
        stopwords = download_stopwords()

    # Strip whitespace around
    stopwords = [stopword.strip() for stopword in stopwords]
    # Extend stopwords with own ones, which were determined after first run
    stopwords = stopwords + STOPWORDS_ADDENDUM

    try:
        cloud = WordCloud(stopwords=stopwords).generate(text)
        cloud.to_file(filename.replace('.html', '.png'))
    except ValueError:
        print('Could not generate word cloud for', key)

def run_single(path):
    soup = extract(path)
    content = transform(soup)
    load(path, content.strip() if content is not None else '')

def run_everything():
    with open('urls.txt', 'r') as fh:
        urls = fh.readlines()
    urls = [url.strip() for url in urls]

    paths = download(urls)
    for path in paths:
        print('Written to', path)
        run_single(path)
    print('Done')

if __name__ == "__main__":
    args = sys.argv

    if len(args) is 1:
      run_everything()
    else:
        if args[1] == 'download':
            download([args[2]])
            print('Done')
        if args[1] == 'parse':
            path = args[2]
            run_single(path)
            print('Done')

Что изменилось? Для одного я скачал Список немецких стоп-слов от github. Таким образом, я могу устранить наиболее распространенные слова из загруженного закона текста.

Затем я создал экземпляр WordCloud со списком загруженных записей POTTWORDS и текста закона. Он будет превращен в изображение с тем же базовым именем.

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

С этим основная часть веб-соскоба завершена.

Бонус: А как насчет спа?

SPAS – или Onlay Page Приложения – это веб-приложения, в которых весь опыт контролируется JavaScript, который выполняется в браузере. Таким образом, загрузка файла HTML не приносит нам далеко. Что мы должны сделать вместо этого?

Мы будем использовать браузер. С селеном. Обязательно Установите драйвер также. Скачать архив .tar.gz и распаковать его в Bin Папка вашей виртуальной среды, поэтому она будет найдена селеном. Это каталог, где вы можете найти активировать Сценарий (на системах GNU/Linux).

В качестве примера я использую Угловой веб-сайт здесь. Angular – это популярное спа-каркас, написанное в JavaScript и гарантированно контролируется им.

Поскольку код будет медленнее, я создаю новый файл под названием Crawler.py для этого. Контент выглядит так:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from wordcloud import WordCloud

def extract(url):
    elem = None
    driver = webdriver.Firefox()
    driver.get(url)

    try:
        found = WebDriverWait(driver, 10).until(
            EC.visibility_of(
                driver.find_element(By.TAG_NAME, "article")
            )
        )
        # Make a copy of relevant data, because Selenium will throw if
        # you try to access the properties after the driver quit
        elem = {
          "text": found.text
        }
    finally:
        driver.close()

    return elem

def transform(elem):
    return elem["text"]
        
def load(text, filepath):
    cloud = WordCloud().generate(text)
    cloud.to_file(filepath)

if __name__ == "__main__":
    url = "https://angular.io/"
    filepath = "angular.png"

    elem = extract(url)
    if elem is not None:
        text = transform(elem)
        load(text, filepath)
    else:
        print("Sorry, could not extract data")

Здесь Python открывает экземпляр Firefox, просматривая веб-сайт и ищет <Статья> элемент. Это копирование над своим текстом в словарь, который прочитан в трансформировать Шаг и превратился в WordCloud во время нагрузка Отказ

При работе с JavaScript-Heake Sites, часто полезно использовать Ждет И, возможно, беги даже execute_script отложить в JavaScript, если это необходимо.

Резюме

Спасибо за чтение этого далеко! Давайте суммируемся тем, что мы узнали сейчас:

  1. Как соскрести сайт с Python’s Запросы упаковка.
  2. Как перевести его в осмысленную структуру, используя Beautifulsoup Отказ
  3. Как дальнейшее обрабатывать эту структуру в то, с чем вы можете работать.
  4. Что делать, если целевая страница опирается на JavaScript.

дальнейшее чтение

Если вы хотите узнать больше обо мне, вы можете Следуй за мной в Twitter или посетить мой сайт Отказ

Я не первый, который писал о веб-соскобке здесь на FreeCodeCamp. Ясоб Халид и Дейв Грей также сделали это в прошлом: