Quét web là quá trình trích xuất dữ liệu cụ thể từ internet một cách tự động. Nó có nhiều trường hợp sử dụng, chẳng hạn như lấy dữ liệu cho dự án máy học, tạo công cụ so sánh giá hoặc bất kỳ ý tưởng sáng tạo nào khác yêu cầu lượng dữ liệu khổng lồ.
Mặc dù về mặt lý thuyết, bạn có thể thực hiện trích xuất dữ liệu theo cách thủ công, nhưng nội dung rộng lớn của Internet khiến phương pháp này không thực tế trong nhiều trường hợp. Vì vậy, biết cách xây dựng một công cụ quét web có thể hữu ích.
Mục đích của bài viết này là hướng dẫn bạn cách tạo một công cụ quét web bằng Python. Bạn sẽ tìm hiểu cách kiểm tra trang web để chuẩn bị quét, trích xuất dữ liệu cụ thể bằng BeautifulSoup, chờ kết xuất JavaScript bằng Selenium và lưu mọi thứ trong tệp JSON hoặc CSV mới.
Nhưng trước tiên, tôi nên cảnh báo bạn về tính hợp pháp của việc quét web. Mặc dù hành động thu thập dữ liệu là hợp pháp nhưng dữ liệu bạn có thể trích xuất có thể là bất hợp pháp khi sử dụng. Đảm bảo rằng bạn không gây rối với bất kỳ:
- Nội dung có bản quyền – vì đó là tài sản trí tuệ của ai đó nên nó được luật pháp bảo vệ và bạn không thể sử dụng lại nội dung đó.
- Dữ liệu cá nhân – nếu thông tin bạn thu thập có thể được sử dụng để nhận dạng một người thì đó được coi là dữ liệu cá nhân và đối với công dân EU, thông tin đó được bảo vệ theo GDPR. Trừ khi bạn có lý do hợp pháp để lưu trữ dữ liệu đó, tốt hơn hết là bỏ qua nó hoàn toàn.
Nói chung, bạn phải luôn đọc các điều khoản và điều kiện của trang web trước khi cạo để đảm bảo rằng bạn không đi ngược lại chính sách của họ. Nếu bạn không chắc chắn về cách tiến hành, hãy liên hệ với chủ sở hữu trang web và yêu cầu sự đồng ý.
Bạn sẽ cần gì cho cái cạp của mình?
Để bắt đầu xây dựng trình quét web của riêng bạn, trước tiên bạn cần cài đặt Python trên máy của mình. Ubuntu 20.04 và các phiên bản Linux khác được cài đặt sẵn Python 3.
Để kiểm tra xem bạn đã cài đặt Python trên thiết bị của mình chưa, hãy chạy lệnh sau:
python3 -v
Nếu bạn đã cài đặt Python, bạn sẽ nhận được kết quả như sau:
Python 3.8.2
Ngoài ra, đối với trình quét web của chúng tôi, chúng tôi sẽ sử dụng các gói Python BeautifulSoup (để chọn dữ liệu cụ thể) và Selenium (để hiển thị nội dung được tải động). Để cài đặt chúng, chỉ cần chạy các lệnh sau:
pip3 install beautifulsoup4
và
pip3 install selenium
Bước cuối cùng là đảm bảo rằng bạn đã cài đặt Google Chrome và Trình điều khiển Chrome trên máy của mình. Những điều này sẽ cần thiết nếu chúng ta muốn sử dụng Selenium để cạo nội dung được tải động.
Cách kiểm tra trang
Bây giờ bạn đã cài đặt mọi thứ, đã đến lúc bắt đầu dự án cạo của chúng tôi một cách nghiêm túc.
Bạn nên chọn trang web bạn muốn cạo dựa trên nhu cầu của bạn. Hãy nhớ rằng mỗi trang web có cấu trúc nội dung khác nhau, vì vậy bạn sẽ cần điều chỉnh những gì bạn học được ở đây khi bạn bắt đầu tự tìm hiểu. Mỗi trang web sẽ yêu cầu những thay đổi nhỏ đối với mã.
Đối với bài viết này, tôi quyết định thu thập thông tin về mười phim đầu tiên trong danh sách 250 phim hàng đầu từ IMDb: https://www.imdb.com/chart/top/.
Đầu tiên, chúng tôi sẽ lấy các tiêu đề, sau đó chúng tôi sẽ đi sâu hơn bằng cách trích xuất thông tin từ các trang của mỗi bộ phim. Một số dữ liệu sẽ yêu cầu kết xuất JavaScript.
Để bắt đầu hiểu cấu trúc của nội dung, bạn nên nhấp chuột phải vào tiêu đề đầu tiên trong danh sách, sau đó chọn “Kiểm tra phần tử”.
Bằng cách nhấn CTRL+F và tìm kiếm trong cấu trúc mã HTML, bạn sẽ thấy rằng chỉ có một đánh dấu trên trang. Điều này rất hữu ích vì nó cung cấp cho chúng tôi thông tin về cách chúng tôi có thể truy cập dữ liệu.
Bộ chọn HTML sẽ cung cấp cho chúng tôi tất cả các tiêu đề từ trang là table tbody tr td.titleColumn a
. Đó là bởi vì tất cả các tiêu đề nằm trong một neo bên trong một ô của bảng với lớp “titleColumn”.
Sử dụng bộ chọn CSS này và nhận nội dung của mỗi neo sẽ cung cấp cho chúng tôi các tiêu đề mà chúng tôi cần. Bạn có thể mô phỏng điều đó trong bảng điều khiển trình duyệt từ cửa sổ mới mà bạn vừa mở và bằng cách sử dụng dòng JavaScript:
document.querySelectorAll("table tbody tr td.titleColumn a")[0].innerText
Bạn sẽ thấy một cái gì đó như thế này:
Bây giờ chúng ta có bộ chọn này, chúng ta có thể bắt đầu viết mã Python và trích xuất thông tin chúng ta cần.
Các tiêu đề phim từ danh sách của chúng tôi là nội dung tĩnh. Đó là bởi vì nếu bạn nhìn vào nguồn trang (CTRL+U trên trang hoặc nhấp chuột phải rồi chọn Xem nguồn trang), bạn sẽ thấy rằng tiêu đề đã có sẵn ở đó.
Nội dung tĩnh thường dễ cạo hơn vì nó không yêu cầu kết xuất JavaScript. Để trích xuất mười tiêu đề đầu tiên trong danh sách, chúng tôi sẽ sử dụng BeautifulSoup để lấy nội dung và sau đó in nội dung đó ở đầu ra của trình cạp của chúng tôi.
import requests
from bs4 import BeautifulSoup
page = requests.get('https://www.imdb.com/chart/top/') # Getting page HTML through request
soup = BeautifulSoup(page.content, 'html.parser') # Parsing content using beautifulsoup
links = soup.select("table tbody tr td.titleColumn a") # Selecting all of the anchors with titles
first10 = links[:10] # Keep only the first 10 anchors
for anchor in first10:
print(anchor.text) # Display the innerText of each anchor
Đoạn mã trên sử dụng bộ chọn mà chúng ta đã thấy trong bước đầu tiên để trích xuất các neo tiêu đề phim từ trang. Sau đó nó lặp qua mười cái đầu tiên và hiển thị nội dung bên trong của mỗi cái.
Đầu ra sẽ trông như thế này:
Khi công nghệ tiên tiến, các trang web bắt đầu tải nội dung của họ một cách linh hoạt. Điều này cải thiện hiệu suất của trang, trải nghiệm của người dùng và thậm chí loại bỏ rào cản bổ sung đối với người dọn dẹp.
Tuy nhiên, điều này làm phức tạp mọi thứ vì HTML được truy xuất từ một yêu cầu đơn giản sẽ không chứa nội dung động. May mắn thay, với Selenium, chúng ta có thể mô phỏng một yêu cầu trong trình duyệt và đợi nội dung động được hiển thị.
Cách sử dụng Selenium cho các yêu cầu
Bạn sẽ cần biết vị trí của chromedriver của mình. Đoạn mã sau giống hệt với đoạn mã được trình bày trong bước thứ hai, nhưng lần này chúng tôi đang sử dụng Selenium để thực hiện yêu cầu. Chúng tôi vẫn sẽ phân tích cú pháp nội dung của trang bằng BeautifulSoup như chúng tôi đã làm trước đây.
from bs4 import BeautifulSoup
from selenium import webdriver
option = webdriver.ChromeOptions()
# I use the following options as my machine is a window subsystem linux.
# I recommend to use the headless option at least, out of the 3
option.add_argument('--headless')
option.add_argument('--no-sandbox')
option.add_argument('--disable-dev-sh-usage')
# Replace YOUR-PATH-TO-CHROMEDRIVER with your chromedriver location
driver = webdriver.Chrome('YOUR-PATH-TO-CHROMEDRIVER', options=option)
driver.get('https://www.imdb.com/chart/top/') # Getting page HTML through request
soup = BeautifulSoup(driver.page_source, 'html.parser') # Parsing content using beautifulsoup. Notice driver.page_source instead of page.content
links = soup.select("table tbody tr td.titleColumn a") # Selecting all of the anchors with titles
first10 = links[:10] # Keep only the first 10 anchors
for anchor in first10:
print(anchor.text) # Display the innerText of each anchor
Đừng quên thay thế “YOUR-PATH-TO-CHROMEDRIVER” bằng vị trí mà bạn đã giải nén chromedriver. Ngoài ra, bạn nên chú ý rằng thay vì page.content
khi chúng tôi đang tạo đối tượng BeautifulSoup, chúng tôi hiện đang sử dụng driver.page_source
cung cấp nội dung HTML của trang.
Sử dụng mã ở trên, bây giờ chúng ta có thể truy cập từng trang phim bằng cách gọi phương thức nhấp chuột trên mỗi neo.
first_link = driver.find_elements_by_css_selector('table tbody tr td.titleColumn a')[0]
first_link.click()
Điều này sẽ mô phỏng một cú nhấp chuột vào liên kết của bộ phim đầu tiên. Tuy nhiên, trong trường hợp này, tôi khuyên bạn nên tiếp tục sử dụng driver.get instead
. Điều này là do bạn sẽ không còn có thể sử dụng click()
phương pháp sau khi bạn chuyển sang một trang khác vì trang mới không có liên kết đến chín bộ phim khác.
Do đó, sau khi nhấp vào tiêu đề đầu tiên trong danh sách, bạn cần quay lại trang đầu tiên, sau đó nhấp vào tiêu đề thứ hai, v.v. Đây là một sự lãng phí hiệu suất và thời gian. Thay vào đó, chúng tôi sẽ chỉ sử dụng các liên kết được trích xuất và truy cập từng liên kết một.
Đối với “The Shawshank Redemption”, trang phim sẽ là https://www.imdb.com/title/tt0111161/. Chúng tôi sẽ trích xuất năm và thời lượng của bộ phim từ trang, nhưng lần này chúng tôi sẽ sử dụng các chức năng của Selenium thay vì BeautifulSoup làm ví dụ. Trong thực tế, bạn có thể sử dụng một trong hai, vì vậy hãy chọn mục yêu thích của bạn.
Để truy xuất năm và thời lượng của bộ phim, bạn nên lặp lại bước đầu tiên mà chúng tôi đã thực hiện ở đây trên trang của bộ phim.
Bạn sẽ nhận thấy rằng bạn có thể tìm thấy tất cả thông tin trong phần tử đầu tiên với lớp ipc-inline-list
(bộ chọn “.ipc-inline-list”) và tất cả các thành phần của danh sách đều chứa thuộc tính role
với giá trị presentation
(các [role=’presentation’]
bộ chọn).
from bs4 import BeautifulSoup
from selenium import webdriver
option = webdriver.ChromeOptions()
# I use the following options as my machine is a window subsystem linux.
# I recommend to use the headless option at least, out of the 3
option.add_argument('--headless')
option.add_argument('--no-sandbox')
option.add_argument('--disable-dev-sh-usage')
# Replace YOUR-PATH-TO-CHROMEDRIVER with your chromedriver location
driver = webdriver.Chrome('YOUR-PATH-TO-CHROMEDRIVER', options=option)
page = driver.get('https://www.imdb.com/chart/top/') # Getting page HTML through request
soup = BeautifulSoup(driver.page_source, 'html.parser') # Parsing content using beautifulsoup
totalScrapedInfo = [] # In this list we will save all the information we scrape
links = soup.select("table tbody tr td.titleColumn a") # Selecting all of the anchors with titles
first10 = links[:10] # Keep only the first 10 anchors
for anchor in first10:
driver.get('https://www.imdb.com/' + anchor['href']) # Access the movie’s page
infolist = driver.find_elements_by_css_selector('.ipc-inline-list')[0] # Find the first element with class ‘ipc-inline-list’
informations = infolist.find_elements_by_css_selector("[role="presentation"]") # Find all elements with role=’presentation’ from the first element with class ‘ipc-inline-list’
scrapedInfo = {
"title": anchor.text,
"year": informations[0].text,
"duration": informations[2].text,
} # Save all the scraped information in a dictionary
totalScrapedInfo.append(scrapedInfo) # Append the dictionary to the totalScrapedInformation list
print(totalScrapedInfo) # Display the list with all the information we scraped
Bước quan trọng tiếp theo trong việc quét web là trích xuất nội dung được tải động. Bạn có thể tìm thấy nội dung như vậy trên mỗi trang của phim (chẳng hạn như https://www.imdb.com/title/tt0111161/) trong phần Danh sách Biên tập.
Nếu bạn xem bằng cách sử dụng kiểm tra trên trang, bạn sẽ thấy rằng bạn có thể tìm thấy phần đó dưới dạng phần tử có thuộc tính data-testid
thiết lập như firstListCardGroup-editorial
. Nhưng nếu bạn tìm trong nguồn trang, bạn sẽ không tìm thấy giá trị thuộc tính này ở bất kỳ đâu. Đó là vì phần Danh sách biên tập được IMDB tải động.
Trong ví dụ sau, chúng tôi sẽ loại bỏ danh sách biên tập của từng bộ phim và thêm nó vào kết quả hiện tại của chúng tôi về tổng thông tin đã loại bỏ.
Để làm điều đó, chúng tôi sẽ nhập thêm một vài gói để có thể chờ tải nội dung động của chúng tôi.
from bs4 import BeautifulSoup
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
option = webdriver.ChromeOptions()
# I use the following options as my machine is a window subsystem linux.
# I recommend to use the headless option at least, out of the 3
option.add_argument('--headless')
option.add_argument('--no-sandbox')
option.add_argument('--disable-dev-sh-usage')
# Replace YOUR-PATH-TO-CHROMEDRIVER with your chromedriver location
driver = webdriver.Chrome('YOUR-PATH-TO-CHROMEDRIVER', options=option)
page = driver.get('https://www.imdb.com/chart/top/') # Getting page HTML through request
soup = BeautifulSoup(driver.page_source, 'html.parser') # Parsing content using beautifulsoup
totalScrapedInfo = [] # In this list we will save all the information we scrape
links = soup.select("table tbody tr td.titleColumn a") # Selecting all of the anchors with titles
first10 = links[:10] # Keep only the first 10 anchors
for anchor in first10:
driver.get('https://www.imdb.com/' + anchor['href']) # Access the movie’s page
infolist = driver.find_elements_by_css_selector('.ipc-inline-list')[0] # Find the first element with class ‘ipc-inline-list’
informations = infolist.find_elements_by_css_selector("[role="presentation"]") # Find all elements with role=’presentation’ from the first element with class ‘ipc-inline-list’
scrapedInfo = {
"title": anchor.text,
"year": informations[0].text,
"duration": informations[2].text,
} # Save all the scraped information in a dictionary
WebDriverWait(driver, 5).until(EC.visibility_of_element_located((By.CSS_SELECTOR, "[data-testid='firstListCardGroup-editorial']"))) # We are waiting for 5 seconds for our element with the attribute data-testid set as `firstListCardGroup-editorial`
listElements = driver.find_elements_by_css_selector("[data-testid='firstListCardGroup-editorial'] .listName") # Extracting the editorial lists elements
listNames = [] # Creating an empty list and then appending only the elements texts
for el in listElements:
listNames.append(el.text)
scrapedInfo['editorial-list'] = listNames # Adding the editorial list names to our scrapedInfo dictionary
totalScrapedInfo.append(scrapedInfo) # Append the dictionary to the totalScrapedInformation list
print(totalScrapedInfo) # Display the list with all the information we scraped
Đối với ví dụ trước, bạn sẽ nhận được đầu ra sau:
Cách lưu nội dung đã cạo
Bây giờ chúng tôi có tất cả dữ liệu mình muốn, chúng tôi có thể lưu nó dưới dạng tệp .json hoặc .csv để dễ đọc hơn.
Để làm điều đó, chúng tôi sẽ chỉ sử dụng các gói JSON và CVS từ Python và ghi nội dung của chúng tôi vào các tệp mới:
import csv
import json
...
file = open('movies.json', mode="w", encoding='utf-8')
file.write(json.dumps(totalScrapedInfo))
writer = csv.writer(open("movies.csv", 'w'))
for movie in totalScrapedInfo:
writer.writerow(movie.values())
Mẹo và thủ thuật cạo
Mặc dù hướng dẫn của chúng tôi cho đến nay đã đủ nâng cao để xử lý các tình huống kết xuất JavaScript, nhưng vẫn còn nhiều điều cần khám phá trong Selenium.
Trong phần này, tôi sẽ chia sẻ một số mẹo và thủ thuật có thể hữu ích.
1. Thời gian yêu cầu của bạn
Nếu bạn spam một máy chủ với hàng trăm yêu cầu trong một thời gian ngắn, rất có thể đến một lúc nào đó, mã captcha sẽ xuất hiện hoặc thậm chí IP của bạn có thể bị chặn. Thật không may, không có cách giải quyết nào trong Python để tránh điều đó.
Do đó, bạn nên đặt một số khoảng thời gian chờ giữa mỗi yêu cầu để lưu lượng truy cập trông tự nhiên hơn.
import time
import requests
page = requests.get('https://www.imdb.com/chart/top/') # Getting page HTML through request
time.sleep(30) # Wait 30 seconds
page = requests.get('https://www.imdb.com/') # Getting page HTML through request
2. Xử lý lỗi
Vì các trang web là động và chúng có thể thay đổi cấu trúc bất kỳ lúc nào, nên việc xử lý lỗi có thể hữu ích nếu bạn thường xuyên sử dụng cùng một công cụ quét web.
try:
WebDriverWait(driver, 5).until(EC.presence_of_element_located((By.CSS_SELECTOR, "your selector")))
break
except TimeoutException:
# If the loading took too long, print message and try again
print("Loading took too much time!")
Cú pháp thử và lỗi có thể hữu ích khi bạn đang đợi một phần tử, giải nén phần tử đó hoặc thậm chí khi bạn chỉ thực hiện yêu cầu.
3. Chụp ảnh màn hình
Nếu bạn cần lấy ảnh chụp màn hình của trang web mà bạn đang quét bất cứ lúc nào, bạn có thể sử dụng:
driver.save_screenshot(‘screenshot-file-name.png’)
Điều này có thể giúp gỡ lỗi khi bạn đang làm việc với nội dung được tải động.
4. Đọc tài liệu
Cuối cùng nhưng không kém phần quan trọng, đừng quên đọc tài liệu từ Selenium. Thư viện này chứa thông tin về cách thực hiện hầu hết các tác vụ bạn có thể thực hiện trong trình duyệt.
Sử dụng Selenium, bạn có thể điền vào biểu mẫu, nhấn nút, trả lời tin nhắn bật lên và làm nhiều điều thú vị khác.
Nếu bạn đang đối mặt với một vấn đề mới, tài liệu của họ có thể là người bạn tốt nhất của bạn.
Suy nghĩ cuối cùng
Mục đích của bài viết này là cung cấp cho bạn phần giới thiệu nâng cao về quét web bằng Python với Selenium và BeautifulSoup. Mặc dù vẫn còn nhiều tính năng từ cả hai công nghệ để khám phá, nhưng giờ đây bạn đã có cơ sở vững chắc về cách bắt đầu tìm hiểu.
Đôi khi việc quét web có thể rất khó khăn vì các trang web bắt đầu ngày càng gây ra nhiều trở ngại hơn cho nhà phát triển. Một số trở ngại này có thể là mã Captcha, khối IP hoặc nội dung động. Vượt qua chúng chỉ bằng Python và Selenium có thể khó hoặc thậm chí là không thể.
Vì vậy, tôi cũng sẽ cung cấp cho bạn một giải pháp thay thế. Hãy thử sử dụng API quét web để giải quyết tất cả những thách thức đó cho bạn. Nó cũng sử dụng proxy luân phiên để bạn không phải lo lắng về việc thêm thời gian chờ giữa các yêu cầu. Chỉ cần nhớ luôn kiểm tra xem dữ liệu bạn muốn có thể được trích xuất và sử dụng hợp pháp hay không.