Chào anh em! Là một Senior Python Dev chuyên về Automation, tôi biết rằng việc trích xuất dữ liệu từ các website (web scraping hay web crawling) là một kỹ năng cực kỳ giá trị. Không chỉ để thu thập thông tin thị trường, nghiên cứu đối thủ, mà còn để tự động hóa các tác vụ lặp đi lặp lại. Trong bài Hướng dẫn crawl dữ liệu bằng Python này, chúng ta sẽ đi thẳng vào việc xây dựng một công cụ scraper thực chiến, chạy được ngay.
Giới thiệu: Tại sao cần giải pháp này?
Thế giới hiện đại vận hành dựa trên dữ liệu. Từ việc phân tích xu hướng thị trường, theo dõi giá sản phẩm của đối thủ, đến xây dựng bộ dữ liệu lớn cho Machine Learning, nhu cầu về dữ liệu là không ngừng. Rất nhiều thông tin giá trị lại nằm ẩn mình trên các trang web, không có API công khai. Đây chính là lúc kỹ năng Hướng dẫn crawl dữ liệu bằng Python trở nên tối quan trọng.
Python, với sự đơn giản, mạnh mẽ và hệ sinh thái thư viện phong phú, là lựa chọn số một cho tác vụ này. Chúng ta sẽ cùng nhau xây dựng một scraper cơ bản nhưng hiệu quả, có khả năng xử lý lỗi và lưu trữ dữ liệu một cách có tổ chức. Mục tiêu là cho anh em một công cụ “chạy được ngay” và dễ dàng tùy chỉnh.
Chuẩn bị: Thư viện cần cài đặt
Trước khi bắt đầu, đảm bảo rằng bạn đã cài đặt Python (phiên bản 3.7+ là lý tưởng). Sau đó, chúng ta sẽ cài đặt các thư viện cần thiết bằng pip:
requests: Để gửi các yêu cầu HTTP (GET, POST) tới website.beautifulsoup4(hoặcbs4): Để phân tích cú pháp HTML/XML, giúp chúng ta tìm kiếm và trích xuất dữ liệu một cách dễ dàng.pandas: Để xử lý và lưu trữ dữ liệu dưới dạng bảng (DataFrames) tiện lợi, đặc biệt là xuất ra file CSV.time: Thư viện tích hợp sẵn của Python, dùng để tạm dừng giữa các yêu cầu, tránh bị chặn.
Mở terminal hoặc command prompt và chạy lệnh sau:
pip install requests beautifulsoup4 pandas
Phân tích Logic: Thuật toán Scraper cơ bản
Chúng ta sẽ xây dựng một scraper hoạt động theo logic sau:
- Gửi yêu cầu HTTP: Sử dụng
requestsđể gửi yêu cầu GET tới URL mục tiêu và nhận về nội dung HTML. - Kiểm tra phản hồi: Đảm bảo yêu cầu thành công (status code 200) trước khi xử lý.
- Phân tích HTML: Dùng
BeautifulSoupđể chuyển đổi nội dung HTML thành một đối tượng dễ dàng tìm kiếm các phần tử (element) mong muốn. - Trích xuất dữ liệu: Xác định các “selector” (dựa trên class, id, tag HTML) của các phần tử chứa dữ liệu (ví dụ: tên sản phẩm, giá, link chi tiết).
- Thu thập và lưu trữ: Lặp qua các phần tử tìm được, trích xuất thông tin, và lưu trữ vào một cấu trúc dữ liệu (ví dụ: list of dictionaries).
- Xử lý phân trang (Pagination): Nếu website có nhiều trang, chúng ta sẽ tự động chuyển sang trang tiếp theo cho đến khi không còn trang nào nữa.
- Lưu vào CSV: Xuất dữ liệu đã thu thập được vào một file CSV để dễ dàng sử dụng sau này.
- Xử lý ngoại lệ: Luôn luôn bao bọc các thao tác mạng và phân tích dữ liệu bằng
try-exceptđể ứng phó với các lỗi có thể xảy ra. - Độ lịch sự: Tạm dừng một chút giữa các yêu cầu để tránh gây quá tải cho server và bị chặn.
Hướng dẫn Code (Step-by-step)
Chúng ta sẽ xây dựng tool từng phần một. Giả sử chúng ta muốn crawl dữ liệu sản phẩm từ một trang web thương mại điện tử (ví dụ: tên sản phẩm, giá, URL chi tiết).
Bước 1: Khởi tạo và Thiết lập Header
Luôn dùng User-Agent để giả lập trình duyệt, tăng khả năng không bị chặn.
import requests
from bs4 import BeautifulSoup
import pandas as pd
import time
import random # Để tạo độ trễ ngẫu nhiên
# Cấu hình User-Agent để giả lập trình duyệt
HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept-Language': 'en-US,en;q=0.9',
'Referer': 'https://www.google.com/' # Thêm Referer có thể giúp ích
}
# URL mục tiêu (thay đổi bằng URL trang web bạn muốn crawl)
# Ví dụ này là giả định một trang sản phẩm có phân trang
BASE_URL = "https://example.com/products" # Thay thế bằng domain thực tế
PAGE_PARAM = "?page=" # Tham số phân trang
Bước 2: Hàm gửi yêu cầu HTTP và lấy nội dung HTML
Hàm này sẽ chịu trách nhiệm gửi request và xử lý các lỗi cơ bản về mạng.
def fetch_page(url):
"""
Gửi yêu cầu HTTP GET đến URL và trả về nội dung HTML.
Xử lý các lỗi mạng và HTTP.
"""
print(f"Đang lấy dữ liệu từ: {url}")
try:
response = requests.get(url, headers=HEADERS, timeout=10)
response.raise_for_status() # Nâng lỗi cho các mã trạng thái HTTP 4xx/5xx
return response.text
except requests.exceptions.HTTPError as e:
print(f"Lỗi HTTP khi truy cập {url}: {e}")
if response.status_code == 403:
print("Có thể bị chặn. Thử thay đổi User-Agent hoặc dùng proxy.")
return None
except requests.exceptions.ConnectionError as e:
print(f"Lỗi kết nối khi truy cập {url}: {e}")
return None
except requests.exceptions.Timeout as e:
print(f"Hết thời gian chờ khi truy cập {url}: {e}")
return None
except requests.exceptions.RequestException as e:
print(f"Lỗi không xác định khi truy cập {url}: {e}")
return None
Bước 3: Hàm phân tích HTML và trích xuất dữ liệu sản phẩm
Đây là phần “xương sống” của scraper, nơi chúng ta dùng BeautifulSoup để tìm các phần tử HTML cụ thể.
def parse_products(html_content):
"""
Phân tích nội dung HTML và trích xuất thông tin sản phẩm.
Trả về một danh sách các dictionary chứa thông tin sản phẩm.
"""
if not html_content:
return []
soup = BeautifulSoup(html_content, 'html.parser')
products_data = []
# Giả định cấu trúc HTML cho một sản phẩm:
# <div class="product-item">
# <a href="/product/san-pham-a-123" class="product-link">
# <h3 class="product-title">Sản phẩm A</h3>
# <span class="product-price">1.234.000 VNĐ</span>
# </a>
# </div>
# Tìm tất cả các div chứa thông tin của mỗi sản phẩm
# Bạn cần kiểm tra cấu trúc HTML thực tế của trang web bạn muốn crawl
product_items = soup.find_all('div', class_='product-item')
if not product_items:
print("Không tìm thấy phần tử sản phẩm nào với selector 'div.product-item'. Kiểm tra lại selector của bạn.")
return []
for item in product_items:
product_info = {}
try:
# Trích xuất Tên sản phẩm
title_tag = item.find('h3', class_='product-title')
product_info['name'] = title_tag.get_text(strip=True) if title_tag else 'N/A'
# Trích xuất Giá sản phẩm
price_tag = item.find('span', class_='product-price')
product_info['price'] = price_tag.get_text(strip=True) if price_tag else 'N/A'
# Trích xuất URL sản phẩm
link_tag = item.find('a', class_='product-link')
if link_tag and 'href' in link_tag.attrs:
# Đảm bảo URL là tuyệt đối nếu nó là tương đối
product_info['url'] = requests.compat.urljoin(BASE_URL, link_tag['href'])
else:
product_info['url'] = 'N/A'
products_data.append(product_info)
except AttributeError as e:
print(f"Lỗi khi trích xuất dữ liệu từ một sản phẩm: {e}. Có thể cấu trúc HTML không nhất quán.")
# Bỏ qua sản phẩm lỗi và tiếp tục
except Exception as e:
print(f"Lỗi không xác định khi xử lý sản phẩm: {e}")
return products_data
Bước 4: Hàm tìm URL trang tiếp theo (Pagination)
Nếu có phân trang, chúng ta cần tìm link để chuyển sang trang kế tiếp.
def get_next_page_url(html_content, current_page_num):
"""
Tìm URL của trang tiếp theo từ nội dung HTML.
Trả về URL của trang kế tiếp hoặc None nếu không còn trang nào.
"""
if not html_content:
return None
soup = BeautifulSoup(html_content, 'html.parser')
# Giả định link trang kế tiếp có class 'next-page' và chứa text 'Trang tiếp'
# Hoặc có thể dựa vào cấu trúc số trang
# Ví dụ: <a href="/products?page=2" class="next-page">Trang tiếp</a>
next_link = soup.find('a', class_='next-page')
if next_link and 'href' in next_link.attrs:
print(f"Tìm thấy link trang tiếp theo: {next_link['href']}")
# Đảm bảo URL là tuyệt đối
return requests.compat.urljoin(BASE_URL, next_link['href'])
# Một cách khác để xử lý phân trang nếu không có link 'next-page' rõ ràng
# Chỉ cần tăng số trang lên
# return f"{BASE_URL}{PAGE_PARAM}{current_page_num + 1}"
print("Không tìm thấy link trang tiếp theo hoặc không còn trang nào.")
return None
Bước 5: Hàm lưu trữ dữ liệu vào CSV
Sử dụng Pandas để tạo DataFrame và xuất ra file CSV.
def save_to_csv(data, filename="products.csv"):
"""
Lưu danh sách các dictionary vào file CSV.
"""
if not data:
print("Không có dữ liệu để lưu.")
return
try:
df = pd.DataFrame(data)
df.to_csv(filename, index=False, encoding='utf-8-sig') # Dùng utf-8-sig để Excel đọc tốt tiếng Việt
print(f"Dữ liệu đã được lưu vào file: {filename}")
except IOError as e:
print(f"Lỗi khi lưu file CSV '{filename}': {e}")
except Exception as e:
print(f"Lỗi không xác định khi lưu dữ liệu vào CSV: {e}")
Bước 6: Logic chính của Scraper (Kết hợp tất cả)
Đây là hàm main sẽ điều phối toàn bộ quá trình crawl.
def main_scraper(start_url, max_pages=5):
"""
Chức năng chính để crawl dữ liệu với phân trang.
"""
all_products = []
current_url = start_url
page_count = 0
while current_url and page_count < max_pages:
page_count += 1
print(f"n--- Đang xử lý Trang {page_count} ---")
html_content = fetch_page(current_url)
if html_content:
products_on_page = parse_products(html_content)
all_products.extend(products_on_page)
print(f"Đã trích xuất {len(products_on_page)} sản phẩm từ trang này.")
# Để trang web không bị quá tải, tạo độ trễ ngẫu nhiên
sleep_time = random.uniform(2, 5) # Ngủ từ 2 đến 5 giây
print(f"Đang tạm dừng {sleep_time:.2f} giây trước khi chuyển trang...")
time.sleep(sleep_time)
# Tìm URL trang tiếp theo
# Nếu website dùng cấu trúc "?page=X", ta có thể tự tăng
# Nếu dùng link ẩn, phải parse HTML
next_page_url_from_html = get_next_page_url(html_content, page_count)
# Cập nhật current_url để chuyển sang trang kế tiếp
if next_page_url_from_html:
current_url = next_page_url_from_html
else:
# Nếu không tìm thấy link next_page rõ ràng, thử tăng số trang thủ công
# Đây là trường hợp nếu website dùng '?page=X' và không có link 'next'
current_url = f"{BASE_URL}{PAGE_PARAM}{page_count + 1}"
print(f"Không tìm thấy link 'next_page' trong HTML, thử dùng URL dự đoán: {current_url}")
# Thêm một kiểm tra để tránh lặp vô hạn nếu trang cuối cùng vẫn trả về nội dung
# Một cách là kiểm tra xem có sản phẩm nào trên trang mới không.
# Nếu page_count > 1 và không có sản phẩm nào, có thể là hết trang.
if page_count > 1 and not products_on_page:
print("Không còn sản phẩm trên trang mới, kết thúc crawl.")
break
else:
print(f"Không lấy được nội dung từ {current_url}. Dừng crawl.")
break
# Nếu url của trang kế tiếp không khác gì url hiện tại thì cũng dừng (trường hợp không tìm thấy link next_page và trang mới không có sản phẩm)
if current_url == f"{BASE_URL}{PAGE_PARAM}{page_count}": # Kiểm tra nếu URL không thay đổi
print("URL không thay đổi, có thể đã hết trang hoặc lỗi logic phân trang. Dừng crawl.")
break
# Thêm một điều kiện dừng khẩn cấp nếu số trang quá lớn
if page_count >= max_pages:
print(f"Đã đạt giới hạn {max_pages} trang, dừng crawl.")
break
print(f"nTổng cộng đã thu thập được {len(all_products)} sản phẩm.")
save_to_csv(all_products, "crawled_products.csv")
Xử lý lỗi thường gặp
Crawl dữ liệu là một cuộc chiến không ngừng với các lỗi. Dưới đây là một số lỗi phổ biến và cách khắc phục:
-
Lỗi HTTP 403 (Forbidden):
- Nguyên nhân: Server phát hiện bạn là bot và chặn truy cập.
- Cách fix:
- User-Agent: Cập nhật
HEADERSvớiUser-Agentmới nhất của một trình duyệt phổ biến. - Tạo độ trễ: Tăng
time.sleep()giữa các request hoặc sử dụngrandom.uniform()để tạo độ trễ ngẫu nhiên. - Proxy/VPN: Dùng dịch vụ proxy hoặc VPN để thay đổi địa chỉ IP.
- Referer: Thêm
Refererheader giả lập bạn đến từ trang nào đó (ví dụ Google).
- User-Agent: Cập nhật
-
Lỗi HTML Structure Changes (Selector không khớp):
- Nguyên nhân: Website thay đổi cấu trúc HTML, class name, ID.
- Cách fix:
- Kiểm tra thủ công: Mở trình duyệt, dùng “Inspect Element” (F12) để kiểm tra lại cấu trúc HTML của các phần tử bạn muốn trích xuất.
- Cập nhật Selector: Sửa lại các selector trong
soup.find(),soup.find_all(). - Selector linh hoạt hơn: Đôi khi dùng selector cha-con hoặc tìm kiếm dựa trên nội dung text có thể bền vững hơn.
-
Lỗi “NoneType” object has no attribute ‘get_text’ (Dữ liệu rỗng):
- Nguyên nhân: Bạn cố gắng gọi một phương thức (như
get_text(),['href']) trên một đối tượngNone. Điều này xảy ra khifind()hoặcfind_all()không tìm thấy phần tử nào. - Cách fix:
- Kiểm tra
None: Luôn kiểm tra xem phần tử có tồn tại không trước khi truy cập thuộc tính của nó (if tag_name: product_info['name'] = tag_name.get_text()). - Xử lý
try/except: Như đã làm trong hàmparse_products, dùngtry-except AttributeErrorđể bắt lỗi và bỏ qua sản phẩm bị lỗi hoặc gán giá trị mặc định.
- Kiểm tra
- Nguyên nhân: Bạn cố gắng gọi một phương thức (như
-
Lỗi Rate Limiting (Quá nhiều yêu cầu):
- Nguyên nhân: Bạn gửi quá nhiều yêu cầu trong một khoảng thời gian ngắn, server phát hiện và hạn chế/chặn IP của bạn.
- Cách fix:
time.sleep(): Bắt buộc phải có độ trễ giữa các request. Thậm chí dùngrandom.uniform()để độ trễ không đều, trông tự nhiên hơn.- Request sessions: Sử dụng
requests.Session()để tái sử dụng kết nối TCP, giảm overhead.
-
Lỗi Hết thời gian chờ (Timeout):
- Nguyên nhân: Server phản hồi quá chậm hoặc không phản hồi.
- Cách fix:
- Tăng timeout: Trong
requests.get(), tăng giá trịtimeoutlên (ví dụ:timeout=30). - Retry logic: Có thể xây dựng một hàm retry tự động gửi lại yêu cầu nếu gặp timeout hoặc lỗi kết nối.
- Tăng timeout: Trong
Kết luận & Source code toàn văn
Chúc mừng! Bạn đã hoàn thành việc xây dựng một scraper cơ bản nhưng vô cùng mạnh mẽ bằng Python. Kỹ thuật crawling là một công cụ cực kỳ hữu ích trong bộ kỹ năng của một Senior Dev, giúp bạn mở khóa tiềm năng của dữ liệu trên web. Hãy luôn nhớ rằng, việc crawl dữ liệu cần tuân thủ các nguyên tắc đạo đức và pháp lý:
- Kiểm tra file
robots.txtcủa website (https://example.com/robots.txt). - Đọc kỹ Điều khoản dịch vụ (Terms of Service) của trang web.
- Không gây quá tải cho server bằng cách gửi quá nhiều request trong thời gian ngắn.
Hãy thử nghiệm với các website khác, điều chỉnh các selector và mở rộng chức năng để trích xuất nhiều loại dữ liệu khác nhau. Thực hành là cách tốt nhất để bạn trở thành chuyên gia!
Dưới đây là toàn bộ source code bạn có thể copy và chạy ngay:
import requests
from bs4 import BeautifulSoup
import pandas as pd
import time
import random
# --- Cấu hình chung ---
HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept-Language': 'en-US,en;q=0.9',
'Referer': 'https://www.google.com/'
}
# Thay đổi bằng URL trang web bạn muốn crawl
# Đây là URL giả định cho ví dụ, bạn cần thay bằng một trang web thực tế
# Ví dụ: "https://www.thegioididong.com/dtdd" hoặc "https://tiki.vn/dien-thoai-may-tinh-bang/c1788"
# Lưu ý: Các website thực tế thường có cấu trúc phức tạp hơn và có thể áp dụng các biện pháp chống crawl
# Cần điều chỉnh selector và có thể cần proxy/captcha solving cho website lớn.
BASE_URL = "https://example.com/products"
# Tham số phân trang, nếu website dùng '?page=X'
# Nếu website có cấu trúc khác (ví dụ: '/products/page/X'), bạn cần điều chỉnh logic `get_next_page_url`
PAGE_PARAM = "?page="
# --- Hàm hỗ trợ ---
def fetch_page(url):
"""
Gửi yêu cầu HTTP GET đến URL và trả về nội dung HTML.
Xử lý các lỗi mạng và HTTP.
"""
print(f"Đang lấy dữ liệu từ: {url}")
try:
response = requests.get(url, headers=HEADERS, timeout=15) # Tăng timeout
response.raise_for_status() # Nâng lỗi cho các mã trạng thái HTTP 4xx/5xx
return response.text
except requests.exceptions.HTTPError as e:
print(f"Lỗi HTTP khi truy cập {url}: {e.response.status_code} - {e.response.reason}")
if e.response.status_code == 403:
print("=> Có thể bị chặn. Thử thay đổi User-Agent hoặc dùng proxy.")
return None
except requests.exceptions.ConnectionError as e:
print(f"Lỗi kết nối khi truy cập {url}: {e}")
return None
except requests.exceptions.Timeout as e:
print(f"Hết thời gian chờ khi truy cập {url}: {e}")
return None
except requests.exceptions.RequestException as e:
print(f"Lỗi không xác định khi truy cập {url}: {e}")
return None
def parse_products(html_content):
"""
Phân tích nội dung HTML và trích xuất thông tin sản phẩm.
Trả về một danh sách các dictionary chứa thông tin sản phẩm.
"""
if not html_content:
return []
soup = BeautifulSoup(html_content, 'html.parser')
products_data = []
# Cần điều chỉnh CÁC SELECTOR NÀY dựa trên cấu trúc HTML thực tế của trang web bạn muốn crawl
# Sử dụng công cụ "Inspect Element" (F12) trong trình duyệt để tìm các selector chính xác.
# Ví dụ giả định cấu trúc:
# <div class="product-list">
# <div class="product-item">
# <a href="/product/san-pham-a-123" class="product-link">
# <h3 class="product-title">Sản phẩm A</h3>
# <span class="product-price">1.234.000 VNĐ</span>
# </a>
# </div>
# <!-- Các product-item khác -->
# </div>
product_items = soup.find_all('div', class_='product-item') # Tìm tất cả các div có class 'product-item'
if not product_items:
print("Không tìm thấy phần tử sản phẩm nào với selector 'div.product-item'. Kiểm tra lại selector của bạn.")
return []
for item in product_items:
product_info = {}
try:
# Trích xuất Tên sản phẩm
title_tag = item.find('h3', class_='product-title') # Tìm h3 có class 'product-title'
product_info['name'] = title_tag.get_text(strip=True) if title_tag else 'N/A'
# Trích xuất Giá sản phẩm
price_tag = item.find('span', class_='product-price') # Tìm span có class 'product-price'
product_info['price'] = price_tag.get_text(strip=True) if price_tag else 'N/A'
# Trích xuất URL sản phẩm
link_tag = item.find('a', class_='product-link') # Tìm a có class 'product-link'
if link_tag and 'href' in link_tag.attrs:
# Đảm bảo URL là tuyệt đối
product_info['url'] = requests.compat.urljoin(BASE_URL, link_tag['href'])
else:
product_info['url'] = 'N/A'
products_data.append(product_info)
except AttributeError as e:
print(f"Lỗi khi trích xuất dữ liệu từ một sản phẩm (AttributeError): {e}. Có thể cấu trúc HTML không nhất quán.")
# Có thể log thêm HTML của item để debug: print(item.prettify())
except Exception as e:
print(f"Lỗi không xác định khi xử lý sản phẩm: {e}")
return products_data
def get_next_page_url(html_content, current_page_num):
"""
Tìm URL của trang tiếp theo từ nội dung HTML.
Trả về URL của trang kế tiếp hoặc None nếu không còn trang nào.
"""
if not html_content:
return None
soup = BeautifulSoup(html_content, 'html.parser')
# Cần điều chỉnh SELECTOR NÀY dựa trên cấu trúc HTML của phần phân trang
# Ví dụ: <a href="/products?page=2" class="next-page">Trang tiếp</a>
# Hoặc <a class="pagination-link" rel="next" href="/products?page=2">Next</a>
next_link = soup.find('a', class_='next-page') # Tìm thẻ a có class 'next-page'
if next_link and 'href' in next_link.attrs:
print(f"Tìm thấy link trang tiếp theo trong HTML: {next_link['href']}")
# Đảm bảo URL là tuyệt đối
return requests.compat.urljoin(BASE_URL, next_link['href'])
# Nếu không có link next_page rõ ràng, thử dự đoán URL tiếp theo
# Áp dụng cho website dùng '?page=X' và không có link next_page trên HTML
predicted_next_url = f"{BASE_URL}{PAGE_PARAM}{current_page_num + 1}"
print(f"Không tìm thấy link 'next_page' trong HTML, dự đoán URL: {predicted_next_url}")
return predicted_next_url
def save_to_csv(data, filename="crawled_products.csv"):
"""
Lưu danh sách các dictionary vào file CSV.
"""
if not data:
print("Không có dữ liệu để lưu.")
return
try:
df = pd.DataFrame(data)
df.to_csv(filename, index=False, encoding='utf-8-sig') # Dùng utf-8-sig để Excel đọc tốt tiếng Việt
print(f"Dữ liệu đã được lưu vào file: {filename}")
except IOError as e:
print(f"Lỗi khi lưu file CSV '{filename}': {e}")
except Exception as e:
print(f"Lỗi không xác định khi lưu dữ liệu vào CSV: {e}")
# --- Logic chính của Scraper ---
def main_scraper(start_url, max_pages=10): # Giới hạn 10 trang để tránh crawl quá nhiều
"""
Chức năng chính để crawl dữ liệu với phân trang.
"""
all_products = []
current_url = start_url
page_count = 0
while current_url and page_count < max_pages:
page_count += 1
print(f"n--- Đang xử lý Trang {page_count} ---")
html_content = fetch_page(current_url)
if html_content:
products_on_page = parse_products(html_content)
all_products.extend(products_on_page)
print(f"Đã trích xuất {len(products_on_page)} sản phẩm từ trang này.")
# Kiểm tra nếu trang hiện tại không có sản phẩm nào
# Đây có thể là dấu hiệu của trang cuối hoặc lỗi
if page_count > 1 and not products_on_page:
print("Trang hiện tại không có sản phẩm nào. Có thể đã hết trang hoặc lỗi cấu trúc HTML.")
break # Dừng crawl
# Tìm URL trang tiếp theo
next_url_candidate = get_next_page_url(html_content, page_count)
# Cập nhật current_url để chuyển sang trang kế tiếp
if next_url_candidate and next_url_candidate != current_url:
current_url = next_url_candidate
else:
print("Không tìm thấy URL trang tiếp theo hợp lệ hoặc URL không thay đổi. Dừng crawl.")
break # Dừng crawl nếu không có trang tiếp theo hoặc URL không thay đổi
# Để trang web không bị quá tải, tạo độ trễ ngẫu nhiên
sleep_time = random.uniform(2, 6) # Ngủ từ 2 đến 6 giây
print(f"Đang tạm dừng {sleep_time:.2f} giây trước khi chuyển trang...")
time.sleep(sleep_time)
else:
print(f"Không lấy được nội dung từ {current_url}. Dừng crawl.")
break
# Nếu đã đạt giới hạn số trang
if page_count >= max_pages:
print(f"Đã đạt giới hạn {max_pages} trang, dừng crawl.")
break
print(f"n==============================================")
print(f"Tổng cộng đã thu thập được {len(all_products)} sản phẩm.")
save_to_csv(all_products, "crawled_products.csv")
print(f"==============================================")
# --- Điểm bắt đầu chạy chương trình ---
if __name__ == "__main__":
# URL bắt đầu crawl (thay đổi nếu trang đầu tiên không có tham số page)
# Ví dụ: Nếu trang đầu tiên là 'https://example.com/products', thì truyền nguyên link đó
# Nếu trang đầu tiên là 'https://example.com/products?page=1', thì truyền link đó
# start_url_to_crawl = f"{BASE_URL}{PAGE_PARAM}1"
start_url_to_crawl = BASE_URL # Bắt đầu từ BASE_URL và để get_next_page_url xử lý page 1
print("Bắt đầu quá trình crawl dữ liệu...")
main_scraper(start_url_to_crawl, max_pages=3) # Có thể điều chỉnh max_pages
print("Quá trình crawl đã kết thúc.")
See more: Hướng dẫn crawl dữ liệu bằng Python.
Discover: Python Trick.
