Як скребувати веб-сайти за допомогою Python 3

Веб-скрапінг - це процес вилучення даних з веб-сайтів.

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

Масове зішкріб може спричинити сервер сильному стресу, що може призвести до відмови в обслуговуванні. А ти цього не хочеш.

Хто повинен це прочитати?

Ця стаття призначена для досвідчених читачів. Припустимо, ви вже знайомі з мовою програмування Python.

Як мінімум, ви повинні розуміти розуміння списку, менеджер контексту та функції. Ви також повинні знати, як налаштувати віртуальне середовище.

Ми запустимо код на вашій локальній машині, щоб дослідити деякі веб-сайти. За допомогою деяких налаштувань ви можете зробити так, щоб він працював і на сервері.

Що ви дізнаєтесь з цієї статті

В кінці цієї статті ви знатимете, як завантажити веб-сторінку, проаналізувати її для отримання цікавої інформації та відформатувати її у придатному для подальшої обробки форматі. Це також відомо як ETL.

У цій статті також буде пояснено, що робити, якщо веб-сайт використовує JavaScript для відтворення вмісту (наприклад, React.js або Angular).

Передумови

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

  • beautifulsoup4 (версія 4.9.0 на момент написання)
  • запити (версія 2.23.0 на момент написання)
  • wordcloud (версія 1.17.0 на момент написання статті, необов’язково)
  • селен (версія 3.141.0 на момент написання статті, необов’язково)

Ви можете знайти код цього проекту в цьому сховищі git на GitHub.

Для цього прикладу ми збираємося скасувати Основний закон Федеративної Республіки Німеччина. (Не хвилюйтеся, я перевірив їх Умови використання. Вони пропонують версію XML для машинної обробки, але ця сторінка служить прикладом обробки HTML. Тож це повинно бути добре.)

Крок 1: Завантажте джерело

Перш за все: я створюю файл, що urls.txtмістить усі URL-адреси, які я хочу завантажити:

//www.gesetze-im-internet.de/gg/art_1.html //www.gesetze-im-internet.de/gg/art_2.html //www.gesetze-im-internet.de/gg/art_3.html //www.gesetze-im-internet.de/gg/art_4.html //www.gesetze-im-internet.de/gg/art_5.html //www.gesetze-im-internet.de/gg/art_6.html //www.gesetze-im-internet.de/gg/art_7.html //www.gesetze-im-internet.de/gg/art_8.html //www.gesetze-im-internet.de/gg/art_9.html //www.gesetze-im-internet.de/gg/art_10.html //www.gesetze-im-internet.de/gg/art_11.html //www.gesetze-im-internet.de/gg/art_12.html //www.gesetze-im-internet.de/gg/art_12a.html //www.gesetze-im-internet.de/gg/art_13.html //www.gesetze-im-internet.de/gg/art_14.html //www.gesetze-im-internet.de/gg/art_15.html //www.gesetze-im-internet.de/gg/art_16.html //www.gesetze-im-internet.de/gg/art_16a.html //www.gesetze-im-internet.de/gg/art_17.html //www.gesetze-im-internet.de/gg/art_17a.html //www.gesetze-im-internet.de/gg/art_18.html //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.

У моєму випадку я зрозумів, що хочу текст закону без жодної розмітки. Обгортання елемента має ідентифікатор container. Використовуючи BeautifulSoup, я бачу, що поєднання findі 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. З аргументом downloadта URL-адресою для завантаження python scraper.py download //www.gesetze-im-internet.de/gg/art_1.html. Це не буде обробляти файл.
  3. З аргументом parseі FilePath для синтаксичного аналізу: 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 = '//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 зі списком завантажених стоп-слів та текстом закону. Він перетвориться на зображення з тим самим базовим ім'ям.

Після першого запуску я виявляю, що перелік зупинних слів неповний. Тому я додаю додаткові слова, які хочу виключити з отриманого зображення.

При цьому основна частина веб-вишкрібання завершена.

Бонус: А як щодо СПА?

SPA - або додатки на одній сторінці - це веб-програми, де весь досвід контролюється JavaScript, який виконується у браузері. Таким чином, завантаження файлу HTML нас далеко не приносить. Що нам робити замість цього?

Ми будемо використовувати браузер. З селеном. Не забудьте також встановити драйвер. Завантажте архів .tar.gz і розпакуйте його у binпапці вашого віртуального середовища, щоб він був знайдений Selenium. Це каталог, де ви можете знайти activateсценарій (у системах GNU / Linux).

Як приклад, я використовую тут веб-сайт Angular. Angular - це популярна SPA-фреймворк, написана на 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 = "//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, переглядає веб-сайт і шукає елемент. Він копіює над своїм текстом у словник, який читається на transformетапі і перетворюється на WordCloud під час load.

При роботі з важкими веб-сайтами JavaScript часто корисно використовувати Waits і, можливо, запустити навіть, execute_scriptщоб перейти на JavaScript, якщо це потрібно.

Резюме

Дякуємо, що прочитали це далеко! Давайте узагальнимо те, що ми дізналися зараз:

  1. Як зішкребти веб-сайт за допомогою requestsпакета Python .
  2. Як перевести його у значущу структуру, використовуючи beautifulsoup.
  3. Як далі переробити цю структуру в щось, з чим можна працювати.
  4. Що робити, якщо цільова сторінка покладається на JavaScript.

Подальше читання

Якщо ви хочете дізнатись більше про мене, ви можете стежити за мною у Twitter або відвідати мій веб-сайт.

Я не перший, хто писав про Web Scraping тут на freeCodeCamp. Раніше Ясуб Халід і Дейв Грей також робили це:

Вступ до веб-вишкрібання за допомогою lxml та Python від Timber.io .com / search / photos / web? utm_source = unsplash & utm_medium = referral & utm_content = creditCopyText ... freeCodeCamp.org freeCodeCamp.org Краще скребування веб-сторінок у Python із Selenium, Beautiful Soup та pand by Dave Gray Web Scraping За допомогою мови програмування Python можливо Швидке та ефективне «вишкрібання» даних із мережі. Веб-скрапінг визначається як:> інструмент для перетворення неструктурованих даних в Інтернеті в машиночитані структуровані дані, готові до аналізу. (sou… freeCodeCamp.org freeCodeCamp.org