Python web scraping su BeautifulSoup

Kas yra web scraping ir kodėl jis aktualus

Internete plaukioja neįsivaizduojamas kiekis duomenų – nuo produktų kainų iki naujienų antraščių, nuo oro prognozių iki nekilnojamojo turto skelbimų. Tačiau dažnai šie duomenys yra prieinami tik per naršyklę, o ne per patogią API sąsają. Čia ir prasideda web scraping nuotykis.

Web scraping – tai procesas, kai automatiškai ištraukiame duomenis iš svetainių. Įsivaizduokite, kad jums reikia palyginti 500 skirtingų produktų kainas iš kelių e-parduotuvių. Rankiniu būdu tai užtruktų amžinybę, bet su Python ir BeautifulSoup biblioteka galite tai padaryti per kelias minutes.

Python tapo de facto standartu web scraping srityje dėl kelių priežasčių. Pirma, jis turi paprastą sintaksę, kuri leidžia greitai parašyti funkcinį kodą. Antra, Python ekosistemoje yra puikių bibliotekų, tokių kaip BeautifulSoup, Scrapy ar Selenium. Trečia, Python bendruomenė yra milžiniška, todėl bet kokiai problemai rasite sprendimą Stack Overflow ar kituose forumuose.

BeautifulSoup pagrindai ir aplinkos paruošimas

BeautifulSoup yra Python biblioteka, skirta HTML ir XML dokumentų analizei. Ji sukuria medžio struktūrą iš puslapio turinio, kurią galite lengvai naršyti, ieškoti ir modifikuoti. Pavadinimas kilo iš Luiso Kerolo „Alisa stebuklų šalyje” – ten buvo eilėraštis apie „gražią sriubą”.

Pradėti naudoti BeautifulSoup yra paprasta. Pirmiausia, turite įdiegti biblioteką:

„`
pip install beautifulsoup4
pip install requests
„`

Requests biblioteka reikalinga HTTP užklausoms siųsti – ji leidžia jums gauti svetainės HTML turinį. BeautifulSoup4 yra naujesnė versija (senoji BeautifulSoup3 jau nebėra palaikoma).

Štai paprasčiausias pavyzdys, kaip pradėti:

„`python
from bs4 import BeautifulSoup
import requests

url = ‘https://example.com’
response = requests.get(url)
soup = BeautifulSoup(response.content, ‘html.parser’)

print(soup.prettify())
„`

Šis kodas atsisiunčia svetainės turinį ir išspausdina jį gražiai suformatuotą. Parametras ‘html.parser’ nurodo, kurį parserio variklį naudoti – tai standartinis Python įtaisytasis parseris. Yra ir kitų variantų, pavyzdžiui, ‘lxml’ ar ‘html5lib’, kurie gali būti greitesni ar tiksliau apdoroti sudėtingą HTML.

Elementų paieška ir navigacija HTML medyje

Kai turite BeautifulSoup objektą, prasideda tikrasis darbas – duomenų ištraukimas. BeautifulSoup siūlo kelis būdus rasti elementus HTML struktūroje.

Paprasčiausias metodas yra `find()` ir `find_all()`. Pirmasis randa pirmą atitikmenį, antrasis – visus:

„`python
# Rasti pirmą h1 tagą
header = soup.find(‘h1’)

# Rasti visus nuorodų tagus
links = soup.find_all(‘a’)

# Rasti elementus pagal klasę
articles = soup.find_all(‘div’, class_=’article’)

# Rasti elementą pagal ID
main_content = soup.find(‘div’, id=’main’)
„`

Atkreipkite dėmesį, kad naudojame `class_` su pabraukimu, nes `class` yra rezervuotas Python žodis. Tai viena iš tų smulkmenų, kurios gali suklaidinti pradedančiuosius.

Galite naudoti ir CSS selektorius per `select()` metodą, jei esate įpratę prie jų iš frontend darbo:

„`python
# Rasti visus paragrafus su klase ‘intro’
intros = soup.select(‘p.intro’)

# Rasti visus nuorodas konkrečiame div’e
nav_links = soup.select(‘div#navigation a’)

# Sudėtingesnis selektorius
featured = soup.select(‘article.featured > h2.title’)
„`

CSS selektoriai kartais būna intuityvingesni, ypač jei dirbate su sudėtinga HTML struktūra. Asmeniškai dažnai naudoju `select()`, nes galiu nukopijuoti selektorių tiesiai iš naršyklės Developer Tools.

Duomenų ištraukimas ir valymas

Radus reikiamus elementus, reikia iš jų ištraukti tekstą ar atributus. BeautifulSoup tai daro labai paprastai:

„`python
# Gauti elemento tekstą
title = soup.find(‘h1’).text

# Arba naudojant .string
subtitle = soup.find(‘h2’).string

# Gauti atributo reikšmę
link = soup.find(‘a’)
href = link.get(‘href’)
# Arba: href = link[‘href’]

# Gauti visą tekstą iš elemento ir jo vaikų
content = soup.find(‘div’, class_=’content’).get_text()
„`

Skirtumas tarp `.text`, `.string` ir `.get_text()` gali būti painus. `.string` grąžina tekstą tik jei elementas turi vieną tekstinį vaiką. `.text` ir `.get_text()` grąžina visą tekstą, įskaitant visus vaikinius elementus. `.get_text()` leidžia nurodyti separatorių:

„`python
# Su separatoriumi
text = soup.get_text(separator=’ | ‘, strip=True)
„`

Realybėje duomenys dažnai būna nešvarūs – su papildomais tarpais, naujomis eilutėmis ar kitomis šiukšlėmis. Štai kaip juos išvalyti:

„`python
import re

# Pašalinti papildomus tarpus
clean_text = ‘ ‘.join(text.split())

# Pašalinti specialius simbolius
clean_text = re.sub(r'[^\w\s]’, ”, text)

# Strip metodas pašalina tarpus pradžioje ir pabaigoje
clean_text = text.strip()
„`

Praktikoje dažnai sukuriu atskirą funkciją duomenų valymui, kurią galiu pakartotinai naudoti:

„`python
def clean_data(text):
if text:
text = text.strip()
text = ‘ ‘.join(text.split())
return text
return None
„`

Darbas su lentelėmis ir struktūruotais duomenimis

Lentelės yra viena dažniausių struktūrų, iš kurių reikia ištraukti duomenis. BeautifulSoup puikiai tinka šiai užduočiai, nors kartais reikia šiek tiek kantrybės.

Štai pavyzdys, kaip ištraukti duomenis iš HTML lentelės:

„`python
table = soup.find(‘table’)
rows = table.find_all(‘tr’)

data = []
for row in rows:
cols = row.find_all(‘td’)
cols = [col.text.strip() for col in cols]
data.append(cols)

# Arba su antraštėmis
headers = [th.text.strip() for th in table.find_all(‘th’)]
„`

Jei dirbate su dideliais duomenų kiekiais, pandas biblioteka gali būti jūsų geriausias draugas:

„`python
import pandas as pd

# pandas gali tiesiogiai skaityti HTML lenteles
tables = pd.read_html(str(table))
df = tables[0] # Pirmoji lentelė

# Arba sukurti DataFrame iš BeautifulSoup duomenų
df = pd.DataFrame(data, columns=headers)
„`

Pandas `read_html()` funkcija yra neįtikėtinai galinga – ji automatiškai randa visas lenteles puslapyje ir konvertuoja jas į DataFrame objektus. Tai sutaupo daug laiko, kai dirbate su statistiniais duomenimis ar finansinėmis ataskaitomis.

Sudėtingesni scenarijai ir klaidos

Realybėje web scraping retai būna toks paprastas kaip pavyzdžiuose. Svetainės turi įvairių apsaugų, dinaminį turinį, keistą HTML struktūrą. Štai keletas dažniausių problemų ir jų sprendimų.

**User-Agent antraštės**: Daugelis svetainių blokuoja užklausas, kurios neturi User-Agent antraštės arba turi įtartiną (pvz., Python-requests/2.28.1):

„`python
headers = {
‘User-Agent’: ‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36’
}
response = requests.get(url, headers=headers)
„`

**Timeout ir klaidos**: Visada nustatykite timeout ir tvarkykite klaidas:

„`python
try:
response = requests.get(url, timeout=10)
response.raise_for_status() # Iškelia klaidą jei status kodas 4xx ar 5xx
except requests.exceptions.RequestException as e:
print(f”Klaida: {e}”)
return None
„`

**Dinaminis turinys**: Jei svetainė naudoja JavaScript duomenims užkrauti, BeautifulSoup jų nematys, nes jis analizuoja tik pradinį HTML. Tokiu atveju reikia Selenium ar Playwright:

„`python
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get(url)

# Palaukti kol elementas pasirodys
element = driver.find_element(By.CLASS_NAME, ‘dynamic-content’)

# Dabar galite naudoti BeautifulSoup
soup = BeautifulSoup(driver.page_source, ‘html.parser’)
driver.quit()
„`

**Rate limiting**: Nesiųskite per daug užklausų per trumpą laiką. Tai ne tik mandagu, bet ir padeda išvengti blokavimo:

„`python
import time

for url in urls:
response = requests.get(url)
# Apdoroti duomenis
time.sleep(2) # Palaukti 2 sekundes
„`

Etika ir teisinis aspektas

Prieš pradedant scrape’inti bet kokią svetainę, svarbu suprasti etinius ir teisinius aspektus. Tai nėra tik teorija – yra buvę teismo bylų dėl web scraping.

Pirmiausia, visada patikrinkite `robots.txt` failą. Jis paprastai yra adresu `https://example.com/robots.txt` ir nurodo, kurias svetainės dalis galima scrape’inti:

„`python
from urllib.robotparser import RobotFileParser

rp = RobotFileParser()
rp.set_url(„https://example.com/robots.txt”)
rp.read()

can_fetch = rp.can_fetch(„*”, „https://example.com/data”)
„`

Antra, skaitykite svetainės naudojimo sąlygas (Terms of Service). Kai kurios svetainės aiškiai draudžia automatinį duomenų rinkimą. Nors tai ne visada teisiškai privaloma, geriau būti atsargiam.

Trečia, neperkraukite serverių. Jei siųsite šimtus užklausų per sekundę, galite ne tik būti užblokuoti, bet ir sukelti serverio problemų. Naudokite protingus intervalus tarp užklausų.

Ketvirta, jei svetainė siūlo API, naudokite jį vietoj scraping. API yra oficialus būdas gauti duomenis, jis stabilesnis ir etiškesnis. Daugelis svetainių turi nemokamus API su protingais limitais.

Penkta, pagalvokite apie duomenų privatumą. Jei scrape’inate asmeninius duomenis, turite laikytis GDPR ir kitų privatumo įstatymų. Tai ypač aktualu Europoje.

Praktiniai patarimai ir geriausia praktika

Dirbdamas su web scraping projektais kelerius metus, surinkau nemažai patirties, kuri gali sutaupyti jums daug laiko ir nervų.

**Testuokite su mažais duomenų kiekiais**: Prieš paleisdami scriptą, kuris scrape’ins tūkstančius puslapių, išbandykite jį su 5-10 puslapių. Taip greičiau rasite klaidas ir neapkrausite serverio.

**Išsaugokite HTML**: Kartais naudinga išsaugoti pradinį HTML failą prieš jį analizuojant. Tai leidžia eksperimentuoti su parserio logika be papildomų užklausų:

„`python
with open(‘page.html’, ‘w’, encoding=’utf-8′) as f:
f.write(response.text)

# Vėliau galite skaityti iš failo
with open(‘page.html’, ‘r’, encoding=’utf-8′) as f:
soup = BeautifulSoup(f, ‘html.parser’)
„`

**Naudokite logging**: Vietoj `print()` naudokite logging modulį. Tai profesionaliau ir lankstesnė:

„`python
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

logger.info(f”Scraping {url}”)
logger.error(f”Failed to parse {url}: {error}”)
„`

**Struktūrizuokite kodą**: Nesukurkite vieno gigantinio scripto. Suskaidykite į funkcijas:

„`python
def fetch_page(url):
# Gauti puslapį
pass

def parse_article(soup):
# Ištraukti straipsnio duomenis
pass

def save_to_database(data):
# Išsaugoti duomenis
pass

def main():
urls = get_urls()
for url in urls:
page = fetch_page(url)
data = parse_article(page)
save_to_database(data)
„`

**Naudokite CSS selektorius iš naršyklės**: Chrome DevTools leidžia dešiniuoju pelės mygtuku spustelėti ant elemento ir pasirinkti „Copy > Copy selector”. Tai sutaupo laiko ieškant tinkamo selektoriaus.

**Tvarkykite koduotes**: HTML puslapiai gali turėti įvairių koduočių. BeautifulSoup paprastai jas atpažįsta automatiškai, bet kartais reikia nurodyti rankiniu būdu:

„`python
soup = BeautifulSoup(response.content, ‘html.parser’, from_encoding=’utf-8′)
„`

Kai BeautifulSoup nebeužtenka ir kas toliau

BeautifulSoup yra puikus įrankis daugumai web scraping užduočių, bet jis turi ribų. Kai projektas auga, gali prireikti galingesnių įrankių.

**Scrapy framework**: Jei kuriate rimtą scraping projektą, Scrapy yra keliu galingesnis. Jis turi integruotą middleware sistemą, automatinį retry mechanizmą, duomenų eksportavimą į įvairius formatus:

„`python
import scrapy

class MySpider(scrapy.Spider):
name = ‘myspider’
start_urls = [‘https://example.com’]

def parse(self, response):
for article in response.css(‘article’):
yield {
‘title’: article.css(‘h2::text’).get(),
‘link’: article.css(‘a::attr(href)’).get(),
}
„`

Scrapy turi daug daugiau funkcionalumo – pipeline’us duomenų apdorojimui, automatinį concurrent requests valdymą, built-in cache sistemą. Bet jis ir sudėtingesnis mokytis.

**Selenium ir Playwright**: Kai reikia scrape’inti JavaScript-heavy svetaines, šie įrankiai valdo tikrą naršyklę. Playwright yra naujesnis ir greitesnis nei Selenium:

„`python
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto(‘https://example.com’)

# Palaukti kol elementas pasirodys
page.wait_for_selector(‘.dynamic-content’)

content = page.content()
soup = BeautifulSoup(content, ‘html.parser’)
„`

**Proxies ir rotating IP**: Jei scrape’inate didelio masto, gali prireikti proxy serverių, kad išvengtumėte blokavimo. Yra komercinių sprendimų kaip ScraperAPI ar Bright Data, kurie automatiškai rotates proxies ir tvarko captchas.

**Headless browsers**: Jei naudojate Selenium ar Playwright, paleiskite juos headless režimu produkcinėje aplinkoje – tai sutaupo resursų:

„`python
options = webdriver.ChromeOptions()
options.add_argument(‘–headless’)
driver = webdriver.Chrome(options=options)
„`

**Duomenų saugojimas**: Pradžioje galite saugoti duomenis CSV ar JSON failuose, bet greitai prireiks duomenų bazės. SQLite yra paprastas startas, PostgreSQL ar MongoDB – rimtesniems projektams:

„`python
import sqlite3

conn = sqlite3.connect(‘scraping.db’)
cursor = conn.cursor()

cursor.execute(”’
CREATE TABLE IF NOT EXISTS articles (
id INTEGER PRIMARY KEY,
title TEXT,
url TEXT,
content TEXT,
scraped_at TIMESTAMP
)
”’)

cursor.execute(‘INSERT INTO articles VALUES (?, ?, ?, ?, ?)’,
(None, title, url, content, datetime.now()))
conn.commit()
„`

Web scraping su BeautifulSoup yra puikus būdas pradėti automatizuoti duomenų rinkimą iš interneto. Nors pradžioje gali atrodyti sudėtinga, su praktika tapsite vis geresni. Svarbiausia – pradėti nuo paprastų projektų, mokytis iš klaidų ir visada laikytis etikos normų. Internetas pilnas duomenų, laukiančių būti išanalizuoti – BeautifulSoup yra jūsų raktas į juos. Sėkmės scrape’inant!

Daugiau

Elasticsearch Logstash pipeline: logų apdorojimas