Как се изстъргват уебсайтове с 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 със списъка с изтеглените от мен дупки и текста на закона. То ще бъде превърнато в изображение със същото базово име.

След първото стартиране откривам, че списъкът със стоп думи е непълен. Затова добавям допълнителни думи, които искам да изключа от полученото изображение.

С това основната част от изстъргването в мрежата е завършена.

Бонус: Какво ще кажете за SPAs?

SPA - или Single Page Applications - са уеб приложения, при които цялото преживяване се контролира от 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 Въведение в уеб изстъргване с lxml и PythonPhoto от Fabian Grohs [// unsplash.com/photos/dC6Pb2JdAqs?utm_source=unsplash&utm_medium=referral&utm_contentext // unconpyplansteh == кредит UnsplansTashplash .com / search / photos / web? utm_source = unsplash & utm_medium = referral & utm_content = creditCopyText ... freeCodeCamp.org freeCodeCamp.org По-добро изстъргване на уеб в Python с Selenium, Beautiful Soup и pandas от Dave Gray Web Scraping С помощта на езика за програмиране Python е възможно да „Изстъргвайте“ данни от мрежата по бърз и ефективен начин. Уеб изстъргването се определя като:> инструмент за превръщане на неструктурираните данни в мрежата в машинно четими, структурирани данни, които са готови за анализ. (sou ... freeCodeCamp.org freeCodeCamp.org