Cách cạo các trang web bằng Python 3

Web cạo là quá trình trích xuất dữ liệu từ các trang web.

Trước khi cố gắng xử lý một trang web, bạn nên đảm bảo rằng nhà cung cấp cho phép nó trong điều khoản dịch vụ của họ. Bạn cũng nên kiểm tra xem liệu bạn có thể sử dụng API thay thế hay không.

Việc cạo hàng loạt có thể khiến máy chủ bị căng thẳng và có thể dẫn đến việc từ chối dịch vụ. Và bạn không muốn điều đó.

Ai nên đọc cái này?

Bài viết này dành cho độc giả nâng cao. Nó sẽ cho rằng bạn đã quen thuộc với ngôn ngữ lập trình Python.

Ở mức tối thiểu, bạn nên hiểu khả năng hiểu danh sách, trình quản lý ngữ cảnh và các chức năng. Bạn cũng nên biết cách thiết lập môi trường ảo.

Chúng tôi sẽ chạy mã trên máy cục bộ của bạn để khám phá một số trang web. Với một số chỉnh sửa, bạn cũng có thể làm cho nó chạy trên máy chủ.

Bạn sẽ học được gì trong bài viết này

Ở cuối bài viết này, bạn sẽ biết cách tải xuống một trang web, phân tích cú pháp để tìm thông tin thú vị và định dạng nó theo định dạng có thể sử dụng để xử lý thêm. Đây còn được gọi là ETL.

Bài viết này cũng sẽ giải thích những việc cần làm nếu trang web đó đang sử dụng JavaScript để hiển thị nội dung (như React.js hoặc Angular).

Điều kiện tiên quyết

Trước khi có thể bắt đầu, tôi muốn đảm bảo rằng chúng tôi đã sẵn sàng để bắt đầu. Vui lòng thiết lập môi trường ảo và cài đặt các gói sau vào đó:

  • beautifulsoup4 (phiên bản 4.9.0 tại thời điểm viết bài)
  • yêu cầu (phiên bản 2.23.0 tại thời điểm viết bài)
  • wordcloud (phiên bản 1.17.0 tại thời điểm viết bài, tùy chọn)
  • selen (phiên bản 3.141.0 tại thời điểm viết bài, tùy chọn)

Bạn có thể tìm thấy mã cho dự án này trong kho git này trên GitHub.

Đối với ví dụ này, chúng tôi sẽ tìm hiểu Luật cơ bản cho Cộng hòa Liên bang Đức. (Đừng lo lắng, tôi đã kiểm tra Điều khoản dịch vụ của họ. Họ cung cấp phiên bản XML để xử lý máy, nhưng trang này đóng vai trò là một ví dụ về xử lý HTML. Vì vậy, nó sẽ ổn thôi.)

Bước 1: Tải xuống nguồn

Điều đầu tiên trước tiên: Tôi tạo một tệp urls.txtchứa tất cả các URL mà tôi muốn tải xuống:

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

Tiếp theo, tôi viết một chút mã Python trong một tệp được gọi là scraper.pyđể tải xuống HTML của các tệp này.

Trong một kịch bản thực tế, điều này sẽ quá đắt và thay vào đó bạn phải sử dụng cơ sở dữ liệu. Để mọi thứ đơn giản, tôi sẽ tải các tệp vào cùng một thư mục bên cạnh cửa hàng và sử dụng tên của chúng làm tên tệp.

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)

Bằng cách tải xuống các tệp, tôi có thể xử lý chúng cục bộ tùy thích mà không bị phụ thuộc vào máy chủ. Cố gắng trở thành một công dân web tốt, được không?

Bước 2: Phân tích cú pháp nguồn

Bây giờ tôi đã tải xuống các tệp, đã đến lúc giải nén các tính năng thú vị của chúng. Do đó, tôi truy cập một trong các trang mà tôi đã tải xuống, mở nó trong trình duyệt web và nhấn Ctrl-U để xem nguồn của nó. Kiểm tra nó sẽ cho tôi thấy cấu trúc HTML.

Trong trường hợp của tôi, tôi nghĩ rằng tôi muốn văn bản của luật mà không có bất kỳ đánh dấu nào. Phần tử bao bọc nó có id là container. Sử dụng BeautifulSoup, tôi có thể thấy rằng sự kết hợp của findget_textsẽ làm những gì tôi muốn.

Vì bây giờ tôi có bước thứ hai, tôi sẽ cấu trúc lại mã một chút bằng cách đưa nó vào các hàm và thêm một CLI tối thiểu.

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) 

Bây giờ tôi có thể chạy mã theo ba cách:

  1. Không có bất kỳ đối số nào để chạy mọi thứ (nghĩa là tải xuống tất cả các URL và giải nén chúng, sau đó lưu vào đĩa) qua: python scraper.py
  2. Với một đối số downloadvà một url để tải xuống python scraper.py download //www.gesetze-im-internet.de/gg/art_1.html. Thao tác này sẽ không xử lý tệp.
  3. Với một cuộc tranh cãi về parsevà một filepath để phân tích cú pháp: python scraper.py art_1.html. Thao tác này sẽ bỏ qua bước tải xuống.

Cùng với đó, còn thiếu một thứ cuối cùng.

Bước 3: Định dạng nguồn để xử lý thêm

Giả sử tôi muốn tạo một đám mây từ cho mỗi bài viết. Đây có thể là một cách nhanh chóng để có được ý tưởng về nội dung của văn bản. Đối với điều này, hãy cài đặt gói wordcloudvà cập nhật tệp như sau:

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

Những gì đã thay đổi? Đối với một, tôi đã tải xuống danh sách các từ dừng tiếng Đức từ GitHub. Bằng cách này, tôi có thể loại bỏ những từ phổ biến nhất khỏi văn bản luật đã tải xuống.

Sau đó, tôi khởi tạo phiên bản WordCloud với danh sách các từ dừng mà tôi đã tải xuống và văn bản của luật. Nó sẽ được chuyển thành một hình ảnh có cùng tên cơ sở.

Sau lần chạy đầu tiên, tôi phát hiện ra rằng danh sách các từ dừng chưa đầy đủ. Vì vậy, tôi thêm các từ bổ sung mà tôi muốn loại trừ khỏi hình ảnh kết quả.

Như vậy, phần chính của việc quét web đã hoàn tất.

Phần thưởng: Còn các SPA thì sao?

SPA - hoặc Ứng dụng trang đơn - là các ứng dụng web trong đó toàn bộ trải nghiệm được kiểm soát bởi JavaScript, được thực thi trong trình duyệt. Do đó, việc tải xuống tệp HTML không mang lại cho chúng tôi quá nhiều. Thay vào đó chúng ta nên làm gì?

Chúng tôi sẽ sử dụng trình duyệt. Với Selenium. Đảm bảo cũng cài đặt trình điều khiển. Tải xuống tệp lưu trữ .tar.gz và giải nén nó trong binthư mục của môi trường ảo của bạn để Selenium tìm thấy nó. Đó là thư mục mà bạn có thể tìm thấy activatescript (trên hệ thống GNU / Linux).

Ví dụ, tôi đang sử dụng trang web Angular ở đây. Angular là một SPA-Framework phổ biến được viết bằng JavaScript và được đảm bảo sẽ được kiểm soát bởi nó trong thời điểm hiện tại.

Vì mã sẽ chậm hơn, tôi tạo một tệp mới được gọi crawler.pycho nó. Nội dung như sau:

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

Ở đây, Python đang mở một phiên bản Firefox, duyệt trang web và tìm kiếm một phần tử. Nó đang sao chép văn bản của nó vào một từ điển, nó sẽ được đọc ra trong transformbước và chuyển thành WordCloud trong suốt thời gian đó load.

Khi xử lý các trang web sử dụng nhiều JavaScript, việc sử dụng Waits thường rất hữu ích và có thể chạy thậm chí execute_scripthoãn lại JavaScript nếu cần.

Tóm lược

Cảm ơn đã đọc đến đây! Hãy tóm tắt những gì chúng ta đã học được bây giờ:

  1. Cách quét một trang web bằng requestsgói của Python .
  2. Làm thế nào để dịch nó thành một cấu trúc có nghĩa bằng cách sử dụng beautifulsoup.
  3. Cách tiếp tục xử lý cấu trúc đó thành thứ mà bạn có thể làm việc.
  4. Phải làm gì nếu trang mục tiêu dựa trên JavaScript.

đọc thêm

Nếu bạn muốn tìm hiểu thêm về tôi, bạn có thể theo dõi tôi trên Twitter hoặc truy cập trang web của tôi.

Tôi không phải là người đầu tiên viết về Web Scraping ở đây trên freeCodeCamp. Yasoob Khalid và Dave Grey cũng đã làm như vậy trong quá khứ:

Giới thiệu về Web Scraping với lxml và Python của Timber.io Giới thiệu về Web Scraping với lxml và PythonPhoto của Fabian Grohs [// unsplash.com/photos/dC6Pb2JdAqs?utm_source=unsplash&utm_medium=referral&utm_content=credit] trên Unsplashplash .com / search / photos / web? utm_source = unsplash & utm_medium = giới thiệu & utm_content = creditCopyText… freeCodeCamp.org freeCodeCamp.org Nạo web tốt hơn bằng Python với Selenium, Beautiful Soup và pandas by Dave Grey Web Scraping Sử dụng ngôn ngữ lập trình Python, có thể "Thu thập" dữ liệu từ trang web một cách nhanh chóng và hiệu quả. Web cạo được định nghĩa là:> một công cụ để biến dữ liệu phi cấu trúc trên web thành dữ liệu có cấu trúc, có thể đọc được bằng máy sẵn sàng để phân tích. (sou… freeCodeCamp.org freeCodeCamp.org