Kako struganje web stranica Pythonom 3

Web struganje je postupak izdvajanja podataka s web stranica.

Prije pokušaja struganja web mjesta, trebali biste biti sigurni da to pružatelj usluga dopušta u svojim uvjetima usluge. Također biste trebali provjeriti možete li umjesto toga koristiti API.

Masivno struganje može poslužitelj stvoriti pod velikim stresom što može rezultirati uskraćivanjem usluge. A ti to ne želiš.

Tko bi ovo trebao pročitati?

Ovaj je članak namijenjen naprednim čitateljima. Pretpostavit će da ste već upoznati s programskim jezikom Python.

U najmanju ruku trebali biste razumjeti razumijevanje popisa, upravitelj konteksta i funkcije. Također biste trebali znati kako postaviti virtualno okruženje.

Pokrenut ćemo kôd na vašem lokalnom računalu kako bismo istražili neke web stranice. Uz neke dorade možete ga pokrenuti i na poslužitelju.

Što ćete naučiti u ovom članku

Na kraju ovog članka znat ćete kako preuzeti web stranicu, raščlaniti je za zanimljive informacije i oblikovati u upotrebljivi format za daljnju obradu. Ovo je također poznato i kao ETL.

Ovaj će članak također objasniti što učiniti ako to web mjesto koristi JavaScript za generiranje sadržaja (poput React.js ili Angular).

Preduvjeti

Prije nego što započnem, želim se uvjeriti da smo spremni. Postavite virtualno okruženje i u njega instalirajte sljedeće pakete:

  • beautifulsoup4 (verzija 4.9.0 u vrijeme pisanja ovog članka)
  • zahtjevi (verzija 2.23.0 u vrijeme pisanja)
  • wordcloud (verzija 1.17.0 u vrijeme pisanja, nije obavezna)
  • selen (verzija 3.141.0 u vrijeme pisanja, nije obavezna)

Kôd za ovaj projekt možete pronaći u ovom git spremištu na GitHubu.

U ovom ćemo primjeru ukinuti temeljni zakon Savezne Republike Njemačke. (Ne brinite, provjerio sam njihove Uvjete pružanja usluge. Oni nude XML verziju za strojnu obradu, ali ova stranica služi kao primjer obrade HTML-a. Stoga bi trebalo biti u redu.)

1. korak: preuzmite izvor

Prvo, prvo: stvorim datoteku urls.txtkoja sadrži sve URL-ove koje želim preuzeti:

//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

Dalje, napišem malo Python koda u datoteku pozvanu scraper.pyza preuzimanje HTML-a ovih datoteka.

U stvarnom scenariju to bi bilo preskupo i umjesto toga biste koristili bazu podataka. Da sve bude jednostavnije, preuzet ću datoteke u isti direktorij pokraj trgovine i koristiti ću njihovo ime kao naziv datoteke.

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)

Preuzimanjem datoteka mogu ih lokalno obraditi koliko god želim, a da ne ovisim o poslužitelju. Pokušaj biti dobar građanin weba, u redu?

Korak 2: Analizirajte izvor

Sad kad sam preuzeo datoteke, vrijeme je da izdvojim njihove zanimljive značajke. Stoga idem na jednu od stranica koje sam preuzeo, otvorim je u web pregledniku i pritisnem Ctrl-U da bih pogledao njezin izvor. Ako ga pregledate, pokazat će mi HTML strukturu.

U mom sam slučaju zaključio da želim tekst zakona bez ikakvog označavanja. Element koji ga obavija ima id container. Korištenjem BeautifulSoupa vidim da je kombinacija findi get_textučinit ću ono što želim.

Budući da sada imam drugi korak, malo ću refaktorizirati kôd stavljajući ga u funkcije i dodati minimalni 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) 

Sada kôd mogu pokrenuti na tri načina:

  1. Bez ikakvih argumenata za pokretanje svega (odnosno, preuzimanje svih URL-ova i njihovo izdvajanje, a zatim spremanje na disk) putem: python scraper.py
  2. Uz argument downloadi url za preuzimanje python scraper.py download //www.gesetze-im-internet.de/gg/art_1.html. Ovo neće obraditi datoteku.
  3. Uz argument parsei filepath analizirati: python scraper.py art_1.html. Ovo će preskočiti korak preuzimanja.

Uz to, nedostaje još jedna posljednja stvar.

Korak 3: Formatirajte izvor za daljnju obradu

Recimo da želim generirati oblak riječi za svaki članak. Ovo može biti brz način da steknete ideju o čemu se radi u tekstu. Za ovo instalirajte paket wordcloudi ažurirajte datoteku ovako:

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')

Što se promijenilo? Za jedan sam s GitHub-a preuzeo popis njemačkih lozinica. Na ovaj način mogu ukloniti najčešće riječi iz preuzetog zakona.

Zatim instanciram WordCloud instancu s popisom zaustavljenih riječi koje sam preuzeo i tekstom zakona. Pretvorit će se u sliku s istim osnovnim imenom.

Nakon prvog pokretanja otkrivam da je popis zaustavljenih riječi nepotpun. Stoga dodajem dodatne riječi koje želim izuzeti iz rezultirajuće slike.

Time je završen glavni dio struganja s weba.

Bonus: Što je s SPA-ima?

SPA - ili aplikacije s jednom stranicom - web su aplikacije u kojima se cjelokupnim iskustvom kontrolira JavaScript koji se izvršava u pregledniku. Kao takvo, preuzimanje HTML datoteke ne dovodi nas daleko. Što bismo umjesto toga trebali učiniti?

We'll use the browser. With Selenium. Make sure to install a driver also. Download the .tar.gz archive and unpack it in the bin folder of your virtual environment so it will be found by Selenium. That is the directory where you can find the activate script (on GNU/Linux systems).

As an example, I am using the Angular website here. Angular is a popular SPA-Framework written in JavaScript and guaranteed to be controlled by it for the time being.

Since the code will be slower, I create a new file called crawler.py for it. The content looks like this:

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")

Here, Python is opening a Firefox instance, browsing the website and looking for an element. It is copying over its text into a dictionary, which gets read out in the transform step and turned into a WordCloud during load.

When dealing with JavaScript-heavy sites, it is often useful to use Waits and perhaps run even execute_scriptto defer to JavaScript if needed.

Summary

Thanks for reading this far! Let's summarise what we've learned now:

  1. How to scrape a website with Python's requests package.
  2. How to translate it into a meaningful structure using beautifulsoup.
  3. How to further process that structure into something you can work with.
  4. What to do if the target page is relying on JavaScript.

Further reading

If you want to find more about me, you can follow me on Twitter or visit my website.

I'm not the first one who wrote about Web Scraping here on freeCodeCamp. Yasoob Khalid and Dave Gray also did so in the past:

Uvod u struganje weba s lxml i Python Timber.io Uvod u struganje weba lxml i PythonPhoto Fabiana Grohsa [// unsplash.com/photos/dC6Pb2JdAqs?utm_source=unsplash&utm_medium=referral&utm_contentext // uConsplansTashplash = KreditConsplansTashplash = KreditConsplansTashplash = KreditConsplansTashplatzConConsplansTashStartCreditContransplashThisplash .com / search / photos / web? utm_source = unsplash & utm_medium = referral & utm_content = creditCopyText ... freeCodeCamp.org freeCodeCamp.org Bolje struganje weba u Pythonu selenom, lijepom juhom i pandama od strane Dave Greya Web Scraping Korištenjem programskog jezika Python moguće je Brze i učinkovite "struganje" podataka s mreže. Web struganje definirano je kao:> alat za pretvaranje nestrukturiranih podataka na webu u strojno čitljive strukturirane podatke koji su spremni za analizu. (sou… freeCodeCamp.org freeCodeCamp.org