Cách xây dựng một pipeline dữ liệu đào tạo AI với Scrapeless
Advanced Data Extraction Specialist
Tóm tắt:
- Bộ dữ liệu tinh chỉnh là sản phẩm quy trình, không phải là tải xuống. Hướng dẫn này xây dựng một quy trình từ đầu đến cuối: lấy các trang công khai đã được hiển thị thông qua API Scraping Universal Scraping, trích xuất cặp được gán nhãn bằng thư viện chuẩn của Python, và viết định dạng JSONL sẵn sàng cho OpenAI với phân chia huấn luyện/kiểm định.
- Lớp lấy dữ liệu là một HTTP POST cho mỗi trang. Tác nhân
unlocker.webunlockertrả lại HTML đã được hiển thị từPOST /api/v1/unlocker/request— không cần quản lý trình duyệt và không cần xoay vòng proxy bên bạn. - Mọi thứ trước khi tải lên đều hoạt động chỉ với một khóa Scrapeless. Việc demo thu thập bao gồm tất cả 10 trang của một trang trích dẫn công khai và sản xuất 100 ví dụ có giám sát; chỉ công việc tinh chỉnh cuối cùng cần một khóa OpenAI và ngân sách.
- Lỗi định dạng là những lỗi dễ ngăn chặn nhất. Mỗi dòng huấn luyện là một đối tượng
{"messages": [...]}với các lượt hệ thống, người dùng và trợ lý — viết nó vớijson.dumpsvà tệp được phân tích trên lần tải lên đầu tiên. - Nguồn gốc là một phần của bộ dữ liệu. Chỉ có các trang công khai, khối lượng có giới hạn, và các điều khoản của trang web cũng như chỉ thị robot đã được kiểm tra trước khi thu thập — phần trách nhiệm bao gồm những gì dữ liệu huấn luyện thêm vào các câu hỏi trích xuất thông thường.
- Miễn phí để bắt đầu. Tạo khóa API của bạn trên gói miễn phí tại app.scrapeless.com.
Giới thiệu: bộ dữ liệu là phần khó khăn
Tinh chỉnh một LLM là dễ dàng về mặt quản lý — tải lên một tệp, tạo một công việc, chờ đợi. Điều mà công việc không thể sửa chữa là tệp. Một mô hình được tinh chỉnh trên một trăm ví dụ được hình thành tốt, được gán nhãn đúng của nhiệm vụ có thể tốt hơn một mô hình được tinh chỉnh trên mười nghìn dòng ồn ào, và sự khác biệt đó được xác định trước khi quá trình huấn luyện bắt đầu, trong quy trình đã thu thập và định hình dữ liệu.
Quy trình đó là một vấn đề trích xuất dữ liệu cho hầu hết các nhóm, vì kiến thức miền mà bạn cần điều chỉnh sống trên các trang web: mô tả sản phẩm, tài liệu, danh sách, đánh giá, tài liệu tham khảo. Phần lý thuyết của hành trình được đề cập trong hướng dẫn về cách thức hoạt động của việc huấn luyện mô hình AI từ đầu đến cuối; bài viết này là phần thực thi. Bạn sẽ xây dựng một quy trình hoàn chỉnh chống lại một trang demo công khai — quotes.toscrape.com, một trang web được xây dựng cho thực hành trích xuất dữ liệu — và hoàn thành với các tệp train.jsonl và val.jsonl mà điểm cuối tinh chỉnh của OpenAI chấp nhận như vậy.
Nhiệm vụ demo: dạy một mô hình phân tích các câu trích dẫn nổi tiếng. Đủ nhỏ để chạy trong vài phút, cấu trúc chính xác như thực tế.
Quy trình tổng thể
Quy trình có năm giai đoạn, và bốn giai đoạn đầu chạy thật trong hướng dẫn này chỉ với một khóa API Scrapeless:
- Lấy dữ liệu — thu thập mỗi trang đã được hiển thị thông qua API Scraping Universal, một POST cho mỗi trang.
- Khám phá — theo dõi phân trang của trang cho đến khi nó kết thúc, với một giới hạn trang cứng như một ràng buộc an toàn.
- Trích xuất — phân tích văn bản câu trích dẫn và tác giả từ mỗi trang bằng trình phân tích HTML chuẩn của Python.
- Chuyển đổi — biến mỗi cặp thành một ví dụ huấn luyện theo định dạng trò chuyện, tách 80/20, và viết JSONL.
- Huấn luyện — tải lên cả hai tệp và tạo công việc tinh chỉnh (giai đoạn này cần một khóa OpenAI; mã được hiển thị và gán nhãn tương ứng).
Dòng chảy cho mỗi trang: hiển thị hoặc lấy dữ liệu → khám phá trang tiếp theo → trích xuất cặp → chuyển đổi thành ví dụ → lưu dưới dạng JSONL.
Tại sao chọn API Scraping Universal Scraping
API Scraping Universal biến việc thu thập trang thành một cuộc gọi hàm xác định: bạn POST một URL, dịch vụ xử lý việc hiển thị, mở khóa và định tuyến proxy ở phía máy chủ, và bạn nhận lại HTML của trang trong trường data. Đối với người xây dựng bộ dữ liệu, điều đó quan trọng gấp đôi. Đầu tiên, tập hợp dữ liệu vẫn có thể tái tạo — cùng một hình thức yêu cầu hoạt động bất kể nguồn có phải là một trang demo tĩnh hay một catalog sản phẩm nặng JavaScript, vì vậy mã quy trình không thay đổi khi mục tiêu thay đổi. Thứ hai, không có đội trình duyệt cục bộ: một bộ dữ liệu gồm vài trăm trang là một vòng lặp qua một hàm.
Trang demo ở đây được thiết kế thân thiện. Điểm nhấn của hướng dẫn là hình dạng quy trình; thay thế danh sách URL và trình phân tích khi bạn chỉ định nó đến tập hợp dữ liệu thực của mình, và giữ nguyên các giới hạn về khối lượng.
Điều kiện tiên quyết
- Python 3 với gói
requests— mọi nhập khẩu khác trong quy trình đều là thư viện chuẩn; các lần chạy trong hướng dẫn này sử dụng Python 3.12. - Một khóa API Scrapeless — tài liệu dành cho nhà phát triển đề cập đến cách tạo khóa.
- Chỉ cho Giai đoạn 5: một khóa API OpenAI với quyền truy cập và ngân sách tinh chỉnh. Các giai đoạn 1–4 chạy mà không cần nó.
Xuất khóa SCRAPELESS để các kịch bản đọc từ môi trường:
bash
export SCRAPELESS_API_KEY="sk_your_key_here"
Giai đoạn 1 - Lấy các trang đã được render
Một POST trả về một trang đã được render. Tác nhân unlocker.webunlocker nhận URL mục tiêu trong input và trả lời với HTML trong trường data của phản hồi:
python
# fetch_page.py — Giai đoạn 1: lấy một trang đã render qua Scrapeless
import os
import requests
ENDPOINT = "https://api.scrapeless.com/api/v1/unlocker/request"
HEADERS = {
"Content-Type": "application/json",
"x-api-token": os.environ["SCRAPELESS_API_KEY"],
}
def fetch_page(url: str) -> str:
resp = requests.post(
ENDPOINT,
headers=HEADERS,
json={"actor": "unlocker.webunlocker", "input": {"url": url, "method": "GET"}},
timeout=120,
)
resp.raise_for_status()
return resp.json().get("data", "")
html = fetch_page("https://quotes.toscrape.com/page/1/")
print(f"đã lấy {len(html):,} ký tự HTML")
print("đánh dấu trích dẫn có mặt:", '<span class="text"' in html)
print("đánh dấu tác giả có mặt:", '<small class="author"' in html)
Đối với trang 1 của trang trích dẫn, điều này trả về 11,021 ký tự HTML với cả hai dấu hiệu có mặt. raise_for_status() giữ cho các thất bại được báo cáo rõ ràng: một khóa sai hoặc một mục tiêu không thể tiếp cận sẽ dừng pipeline thay vì ghi một tập hợp rỗng.
Giai đoạn 2 - Khám phá toàn bộ tập hợp
Trang web cho bạn biết nơi nó kết thúc, vì vậy trình thu thập theo dõi trang web thay vì đoán URL. Mỗi trang của trang demo chứa một phần tử li class="next" cho đến trang cuối cùng; vòng lặp thu thập dữ liệu lấy, kiểm tra dấu hiệu đó và tiến tới bộ đếm trang. Hai giới hạn giữ cho giai đoạn này trung thực: vòng lặp dừng khi dấu hiệu biến mất, và một giới hạn MAX_PAGES dừng nó ngay cả khi dấu hiệu không bao giờ biến mất. Giới hạn là sự khác biệt giữa việc xây dựng tập dữ liệu và một trình thu thập không giới hạn — đặt nó theo kích thước tập hợp mà bạn thực sự định thu thập.
Vòng lặp tự nó là bốn dòng trong toàn bộ kịch bản pipeline ở Giai đoạn 4.
Giai đoạn 3 - Trích xuất cặp trích dẫn-tác giả
Việc trích xuất biến HTML thành các cặp có nhãn, và thư viện tiêu chuẩn là đủ cho việc này. html.parser.HTMLParser kích hoạt các callback per tag; theo dõi hai cờ trong khi đi qua trang thu thập văn bản và tác giả của mỗi trích dẫn mà không cần bất kỳ phụ thuộc bên thứ ba nào:
python
# quote_parser.py — Giai đoạn 3: trích xuất tiêu chuẩn từ (trích dẫn, tác giả)
from html.parser import HTMLParser
class QuoteParser(HTMLParser):
"""Thu thập các cặp (văn bản, tác giả) từ đánh dấu quotes.toscrape.com."""
def __init__(self):
super().__init__()
self.pairs, self._text, self._mode = [], "", None
def handle_starttag(self, tag, attrs):
a = dict(attrs)
if tag == "span" and a.get("class") == "text":
self._mode = "text"
elif tag == "small" and a.get("class") == "author":
self._mode = "author"
def handle_data(self, data):
if self._mode == "text":
self._text = data.strip("“”")
elif self._mode == "author":
self.pairs.append((self._text, data.strip()))
self._mode = None
def parse_quotes(html: str):
p = QuoteParser()
p.feed(html)
return p.pairs
Một thư viện selector cũng có thể hoạt động — lý do để hiển thị phiên bản thư viện tiêu chuẩn là toàn bộ pipeline giữ cho kịch bản này có hai phụ thuộc (requests cùng với Python chính), đó là một thứ ít hơn để gắn khi pipeline chuyển sang một trình lập lịch.
Giai đoạn 4 - Biến đổi sang định dạng chat-format JSONL
Điểm cuối fine-tuning của OpenAI huấn luyện trên các bản sao chat: mỗi dòng của tệp là một đối tượng {"messages": [...]} với quy tắc hệ thống, đầu vào của người dùng và câu trả lời của trợ lý mà bạn muốn mô hình học. Định dạng là JSON Lines — định nghĩa định dạng JSON Lines chính xác là "một giá trị JSON mỗi dòng" — và nghiên cứu về việc tinh chỉnh hướng dẫn như nghiên cứu InstructGPT là lý do hình dạng trông giống như một cuộc trò chuyện: các mô hình theo dõi nhiệm vụ tốt hơn khi được huấn luyện trên các cặp trình diễn.
Kịch bản này là toàn bộ pipeline — Các Giai đoạn 1 đến 4 được kết hợp, kết thúc trong hai tệp:
python
# build_dataset.py — Các Giai đoạn 1–4: thu thập, trích xuất, biến đổi, lưu trữ
import json
import os
import requests
from html.parser import HTMLParser
ENDPOINT = "https://api.scrapeless.com/api/v1/unlocker/request"
HEADERS = {
"Content-Type": "application/json",
"x-api-token": os.environ["SCRAPELESS_API_KEY"],
}
BASE = "https://quotes.toscrape.com"
MAX_PAGES = 15 # giới hạn an toàn trên kích thước thực của trang
SYSTEM = "Bạn ghi công cho các trích dẫn nổi tiếng. Trả lời chỉ với tên của tác giả."
class QuoteParser(HTMLParser):
def __init__(self):
super().__init__()
self.pairs, self._text, self._mode = [], "", None
def handle_starttag(self, tag, attrs):
a = dict(attrs)
if tag == "span" and a.get("class") == "text":
```python
self._mode = "text"
elif tag == "small" and a.get("class") == "author":
self._mode = "author"
def handle_data(self, data):
if self._mode == "text":
self._text = data.strip("“”")
elif self._mode == "author":
self.pairs.append((self._text, data.strip()))
self._mode = None
def fetch_page(url: str) -> str:
resp = requests.post(
ENDPOINT,
headers=HEADERS,
json={"actor": "unlocker.webunlocker", "input": {"url": url, "method": "GET"}},
timeout=120,
)
resp.raise_for_status()
return resp.json().get("data", "")
def to_example(text: str, author: str) -> dict:
return {
"messages": [
{"role": "system", "content": SYSTEM},
{"role": "user", "content": f"Ai đã nói điều này: “{text}”"},
{"role": "assistant", "content": author},
]
}
pairs, page = [], 1
while page <= MAX_PAGES:
html = fetch_page(f"{BASE}/page/{page}/")
parser = QuoteParser()
parser.feed(html)
pairs.extend(parser.pairs)
if 'class="next"' not in html: # Giai đoạn 2: trang web cho biết khi nào nó kết thúc
break
page += 1
examples = [to_example(t, a) for t, a in pairs]
split = int(len(examples) * 0.8)
for name, rows in (("train.jsonl", examples[:split]), ("val.jsonl", examples[split:])):
with open(name, "w", encoding="utf-8") as f:
f.writelines(json.dumps(r, ensure_ascii=False) + "\n" for r in rows)
print(f"Số trang đã quét: {page} | số cặp đã trích xuất: {len(pairs)}")
print(f"train.jsonl: {split} ví dụ | val.jsonl: {len(examples) - split} ví dụ")
print("dòng huấn luyện đầu tiên:")
print(json.dumps(examples[0], ensure_ascii=False)[:180])
Chạy chương trình quét tất cả 10 trang của trang web, trích xuất 100 cặp và ghi 80 ví dụ huấn luyện so với 20 ví dụ xác thực. Sự phân chia 80/20 cung cấp cho công việc tinh chỉnh một cái gì đó để đo lường sự tổng quát — các ví dụ xác thực mà mô hình chưa bao giờ huấn luyện trên. Cả hai tệp đều được ghi với ensure_ascii=False và một json.dumps trên mỗi dòng, đây là bảo hiểm rẻ nhất có thể: JSONL bị lỗi là cách phổ biến để mất lượt truy cập lên điểm cuối tải lên.
Một lần kiểm tra chất lượng là đáng giá để thực hiện bằng tay ngay cả trên một tập hợp nhỏ: đọc một mẫu các dòng và xác nhận rằng nhãn thực sự trả lời đầu vào. Mô hình học bất cứ điều gì tệp thể hiện, bao gồm cả những sai lầm.
Giai đoạn 5 — Nộp công việc tinh chỉnh
Cuộc gọi huấn luyện là nhỏ so với tất cả mọi thứ ở trên. Tải lên cả hai tệp với mục đích fine-tune, sau đó tạo công việc đối với một mô hình có thể tinh chỉnh — danh sách và thông số hiện tại có trong hướng dẫn tinh chỉnh giám sát của OpenAI.
Chú ý: Giai đoạn này là khoảng trống tiên quyết của quy trình - nó cần một
OPENAI_API_KEYvới quyền truy cập và ngân sách tinh chỉnh, điều mà hướng dẫn này không giả định. Mọi thứ ở trên hoạt động thực sự chỉ với một khóa Scrapeless.
python
# submit_job.py — Giai đoạn 5: tải lên tập dữ liệu và tạo công việc (cần OPENAI_API_KEY)
from openai import OpenAI
client = OpenAI() # đọc OPENAI_API_KEY từ môi trường
train = client.files.create(file=open("train.jsonl", "rb"), purpose="fine-tune")
val = client.files.create(file=open("val.jsonl", "rb"), purpose="fine-tune")
job = client.fine_tuning.jobs.create(
training_file=train.id,
validation_file=val.id,
model="gpt-4.1-mini-2025-04-14",
)
print(job.id, job.status)
Khi công việc hoàn tất, id mô hình kết quả sẽ được đưa vào cùng một cuộc gọi hoàn thành trò chuyện mà bạn đã sử dụng — tập dữ liệu quyết định liệu mô hình đó có thực sự trả lời "Albert Einstein" khi được hiển thị một câu trích mà nó chưa bao giờ thấy.
Lấy khóa API của bạn trên kế hoạch miễn phí: app.scrapeless.com
Thu thập dữ liệu một cách có trách nhiệm cho dữ liệu huấn luyện
Dữ liệu huấn luyện mang theo mọi nghĩa vụ mà các trang nguồn mang lại, cộng với một điều nữa: mô hình sẽ tái tạo các mẫu từ bất cứ thứ gì bạn cung cấp cho nó. Bốn thực hành giúp giữ cho việc thu thập có thể bảo vệ được.
-
Chỉ các trang công cộng và đọc các điều khoản. Chỉ thu thập những gì hiển thị mà không cần tài khoản, và kiểm tra điều khoản dịch vụ của trang web mục tiêu trước khi quét — việc sử dụng để huấn luyện được nêu rõ bởi một số lượng ngày càng tăng các trang.
-
Tôn trọng các chỉ thị của robot. Giao thức loại trừ robot (RFC 9309) là tuyên bố có thể đọc được của máy về những gì một trang cho phép các trình quét chạm vào; kiểm tra nó cho các đường dẫn mục tiêu của bạn trước khi thu thập.
-
Giảm thiểu. Lấy các trường mà nhiệm vụ cần — ở đây, văn bản câu trích và tác giả — không phải là các bản sao toàn trang kéo theo các ý kiến người dùng, tên, hoặc dữ liệu khác bị lãng phí. Giới hạn số trang là một phần của việc giảm thiểu.
-
Theo dõi nguồn gốc và cấp phép. Ghi lại nguồn gốc của từng ví dụ và thời gian. Văn bản công khai không tự động được cấp phép cho việc đào tạo mô hình ở mọi khu vực; khi tập dữ liệu nhạy cảm hơn những câu trích dẫn nổi tiếng, hãy để luật sư xem xét vấn đề cấp phép trước khi công việc được thực hiện.
Những gì bạn nhận được
Hai tập tin, sẵn sàng để tải lên. Mỗi dòng là một ví dụ giám sát hoàn chỉnh — dòng đầu tiên của train.jsonl từ chạy trên:
text
{"messages": [{"role": "system", "content": "Bạn gán cho các câu trích dẫn nổi tiếng. Hãy chỉ trả lời bằng tên tác giả."}, {"role": "user", "content": "Ai đã nói câu này: “Thế giới mà chúng ta đã tạo ra là một quá trình tư duy của chúng ta. Nó không thể thay đổi mà không thay đổi cách suy nghĩ của chúng ta.”"}, {"role": "assistant", "content": "Albert Einstein"}]}
Hình dạng không thay đổi khi mở rộng: một tập dữ liệu thực sự thay thế trình phân tích cú pháp và danh sách URL, quy tắc hệ thống mô tả nhiệm vụ của bạn thay vì việc gán các câu trích dẫn, và trình viết JSONL, phân chia, và tải lên vẫn giữ nguyên. Nếu mục tiêu của bạn là truy xuất thay vì cập nhật trọng số, cùng một lớp lấy dữ liệu cung cấp cho pipeline RAG văn bản sạch thay vào đó — các đoạn và nhúng chứ không phải là các ví dụ đào tạo.
Kết luận
Pipeline kiếm tiền ở hai đầu. Ở phía trước, API Scraping Toàn cầu thực hiện việc thu thập theo cách xác định cho mỗi trang, vì vậy tập dữ liệu có thể tái tạo và mã vẫn chạy tốt khi thay đổi mục tiêu. Ở phía sau, việc chuyển đổi có kỷ luật — định dạng trò chuyện chính xác, một đối tượng JSON trên mỗi dòng, một phân chia xác thực thực sự, một lần xử lý của con người đối với các nhãn — là điều phân biệt giữa một lần tinh chỉnh cải thiện câu trả lời và một lần tiêu tốn ngân sách. Giữa hai đầu này, phần ở giữa là một trăm dòng Python từ thư viện tiêu chuẩn mà bạn hiện có.
Sẵn sàng xây dựng pipeline dữ liệu đào tạo của bạn?
Kế hoạch và khối lượng yêu cầu đi kèm có trên trang giá, và lớp lấy dữ liệu trong hướng dẫn này hoạt động trên kế hoạch miễn phí — tạo khóa API của bạn tại app.scrapeless.com và Giai đoạn 1 trả về trang đầu tiên được hiển thị của bạn trong một POST.
Câu hỏi thường gặp
H: Tôi cần bao nhiêu dữ liệu để tinh chỉnh một mô hình?
Ít hơn hầu hết các nhóm mong đợi, nếu các ví dụ sạch. Việc tinh chỉnh giám sát cho thấy sự thay đổi hành vi có thể đo lường với tập dữ liệu từ vài chục đến hàng trăm ví dụ được gán nhãn rõ ràng cho các nhiệm vụ hẹp; những sự thay đổi hành vi rộng cần nhiều hơn. Bắt đầu với tập dữ liệu nhỏ nhất đại diện cho nhiệm vụ, đánh giá so với tệp xác thực, và mở rộng tập dữ liệu nơi mô hình thực sự thất bại.
H: Định dạng nào mà điểm dừng tinh chỉnh mong đợi?
Dòng JSON định dạng trò chuyện: mỗi dòng là một đối tượng JSON độc lập với một mảng messages bao gồm các lượt của hệ thống, người dùng và trợ lý, được tải lên với mục đích fine-tune. Pipeline trong hướng dẫn này viết trực tiếp định dạng đó với json.dumps, một đối tượng mỗi dòng, không có dấu phẩy thừa hoặc mảng bao bọc.
H: Tôi nên tinh chỉnh hay sử dụng RAG?
Tinh chỉnh khi bạn muốn mô hình thay đổi hành vi — giọng điệu, định dạng, phản ứng đặc thù của nhiệm vụ — và truy xuất khi bạn muốn nó biết các sự kiện hiện tại. Cập nhật trọng số định hình các mẫu nhưng trở nên lỗi thời; truy xuất giữ cho thông tin hiện tại nhưng không dạy mô hình thói quen mới. Hai cái này kết hợp với nhau, và cả hai bắt đầu từ cùng một lớp thu thập mà hướng dẫn này xây dựng.
H: Việc đào tạo một mô hình trên dữ liệu thu thập có hợp pháp không?
Điều đó phụ thuộc vào những gì bạn thu thập và nơi bạn hoạt động, và việc đọc các trang công khai không tự động là một giấy phép để đào tạo trên chúng. Điều khoản trang web, chỉ dẫn robot, bản quyền, và các luật về quyền riêng tư liên quan đến bất kỳ dữ liệu cá nhân nào trong tập dữ liệu đều áp dụng — phần trách nhiệm ở trên liệt kê các thực hành làm việc, và đối với bất kỳ thứ gì rõ ràng công khai, không phải dữ liệu cá nhân, câu hỏi cấp phép thuộc về luật sư.
H: Pipeline này có hoạt động cho các mô hình trọng số mở không?
Có — các giai đoạn thu thập và chuyển đổi là không phụ thuộc vào mô hình. JSONL định dạng trò chuyện là yếu tố chung giữa các ngăn xếp tinh chỉnh; các quy trình trọng số mở tiêu thụ cùng một cấu trúc cuộc trò chuyện, vì vậy giai đoạn duy nhất thay đổi là Giai đoạn 5, nơi cuộc gọi tải lên được thay thế bằng bộ tải dữ liệu của khung đào tạo của bạn.
Tại Scrapless, chúng tôi chỉ truy cập dữ liệu có sẵn công khai trong khi tuân thủ nghiêm ngặt các luật, quy định và chính sách bảo mật trang web hiện hành. Nội dung trong blog này chỉ nhằm mục đích trình diễn và không liên quan đến bất kỳ hoạt động bất hợp pháp hoặc vi phạm nào. Chúng tôi không đảm bảo và từ chối mọi trách nhiệm đối với việc sử dụng thông tin từ blog này hoặc các liên kết của bên thứ ba. Trước khi tham gia vào bất kỳ hoạt động cạo nào, hãy tham khảo ý kiến cố vấn pháp lý của bạn và xem xét các điều khoản dịch vụ của trang web mục tiêu hoặc có được các quyền cần thiết.



