Claude Web Scraping: Chuyển đổi bảng thống kê Nullable thành JSON sạch
Specialist in Anti-Bot Strategies
Tóm tắt:
- Claude đọc dữ liệu có thể null một cách rõ ràng, và một lần chạy trực tiếp đã chứng minh điều đó. Khi nhận một bảng thống kê mùa giải đã được kết xuất,
anthropic/claude-haiku-4.5đã trả về 25 bản ghi mùa đội trong một lần gọi, viết chính xácnullcho một thống kê chưa tồn tại trong mùa giải NHL 1990-91 thay vì đoán một giá trị. - API nhận đầu vào là văn bản, đầu ra là văn bản — nó không bao giờ chạm vào mạng. Việc kết xuất, phiên và các thách thức truy cập thuộc về một lớp lấy dữ liệu; hướng dẫn này là một POST cho mỗi trang thông qua API thu thập dữ liệu Scrapeless Universal.
- Chế độ JSON có một điểm thú vị đáng biết trước khi nó gây ra lỗi phân tích cho bạn. Claude qua OpenRouter đôi khi bao bọc đầu ra có cấu trúc trong một hàng rào
```jsonngay cả khiresponse_formatđã được đặt — kịch bản trích xuất bên dưới sẽ loại bỏ nó một cách phòng ngừa. - Di chuyển tên gọi cấp giá rẻ hiện tại. Danh mục mô hình OpenRouter trực tiếp đặt
claude-haiku-4.5cao hơn nhiều so với dòng cũclaude-3-haikuvề khả năng với một khoản phí nhỏ cho mỗi token; hướng dẫn này đã kiểm tra danh mục trực tiếp thay vì giả định một cái tên từ trí nhớ. - Chưa có khóa Anthropic? Yêu cầu giống hệt chạy qua OpenRouter. Hướng dẫn này đã thực hiện nó trực tiếp trên
anthropic/claude-haiku-4.5và cho thấy đầu ra đã được ghi lại. - Miễn phí để bắt đầu ở phía lấy dữ liệu. Tạo khóa API Scrapeless của bạn tại app.scrapeless.com.
Claude có thể thu thập dữ liệu từ các trang web không?
Claude phân tích; nó không lấy dữ liệu. Đưa cho nó văn bản trang và một sơ đồ và nó sẽ trả về các bản ghi sạch sẽ, có kiểu — bao gồm trường hợp khó khăn hơn là biết khi nào một trường thực sự không hiện diện thay vì sáng chế ra một số không hợp lý. Điều nó không thể làm là lấy một URL cụ thể, thực hiện JavaScript tạo ra một trang, giữ một phiên, hoặc vượt qua một thách thức truy cập với khối lượng mà một công việc thu thập dữ liệu cần. Mọi thiết lập "Claude web scraping" hoạt động đều kết hợp mô hình với một lớp lấy dữ liệu làm việc riêng.
Một hướng dẫn trước đó trên trang này, Web Scraping with Claude AI, khảo sát mười cách khác nhau để kết hợp Claude với một trình thu thập. Hướng dẫn này thì hẹp hơn và cụ thể hơn: một mục tiêu thực tế, một lần trích xuất thực hiện trực tiếp, một sơ đồ, và đầu ra đã được ghi lại thực tế. Nếu câu hỏi rộng hơn là mô hình ngôn ngữ nào để sử dụng cho phân tích, LLM scraper explainer đã đề cập đến loại này.
Cài đặt
SDK Anthropic bao phủ đường dẫn tự nhiên, openai bao phủ đường dẫn OpenRouter, và requests bao phủ lớp lấy dữ liệu:
bash
pip install "anthropic==0.120.0" "openai==2.48.0" requests
Cấu hình
bash
export ANTHROPIC_API_KEY="sk-ant-your_key"
export SCRAPELESS_API_KEY="sk_your_scrapeless_key"
Lấy một trang với một trường đôi khi bị thiếu
Mục tiêu demo là một sandbox thống kê thể thao công khai: một hàng theo mùa mỗi đội NHL, bao gồm một thống kê — các trận thua sau thời gian — mà giải đấu không theo dõi cho đến giữa những năm 1990. Các hàng từ trước thời điểm đó để lại ô trống, điều này khiến trang trở thành một bài kiểm tra thực sự về việc liệu một trình trích xuất phát minh ra dữ liệu hay báo cáo rằng nó bị thiếu. Một POST đến Universal Scraping API trả về trang với việc kết xuất và định tuyến proxy được xử lý ở phía máy chủ:
python
# fetch_teams.py — lấy trang thống kê đội qua Scrapeless
import os
import requests
resp = requests.post(
"https://api.scrapeless.com/api/v2/unlocker/request",
headers={
"Content-Type": "application/json",
"x-api-token": os.environ["SCRAPELESS_API_KEY"],
},
json={
"actor": "unlocker.webunlocker",
"input": {"url": "https://scrapethissite.com/pages/forms/", "method": "GET", "js_render": True},
},
timeout=120,
)
resp.raise_for_status()
html = resp.json().get("data", "")
print(f"đã lấy {len(html):,} ký tự")
print("hàng đội:", html.count('<tr class="team">'))
with open("page.html", "w", encoding="utf-8") as f:
f.write(html)
Chạy lệnh in ra 25 hàng đội cho trang đầu tiên của kết quả sandbox — một mục tiêu trực tiếp được duy trì bởi một trang web thực hành thu thập dữ liệu công khai, khác với các trích dẫn và sách đã sử dụng trong các hướng dẫn phân tích LLM khác của trang này.
Triển khai cơ bản: Claude như là trình trích xuất
API Messages gốc sử dụng JSON Schema trực tiếp thông qua output_config, mà tài liệu tham khảo API hiện tại ghi nhận là sự thay thế cho các thủ thuật JSON dựa trên prefill cũ — xem hướng dẫn đầu ra có cấu trúc của Anthropic. Cấp độ Claude không phải kế thừa rẻ nhất hiện tại là claude-haiku-4-5; lưu ý rằng ID mô hình gốc sử dụng dấu gạch ngang, khác với slug claude-haiku-4.5 mà OpenRouter liệt kê.
Chú ý: Khối này cần một
ANTHROPIC_API_KEYcó tín dụng — điều kiện tiên quyết duy nhất mà hướng dẫn này không giả định. Phần tiếp theo thực hiện quá trình trích xuất giống hệt trực tiếp qua OpenRouter, với đầu ra được ghi lại.
python
# extract_claude.py — trích xuất Claude gốc (cần ANTHROPIC_API_KEY)
import json
from anthropic import Anthropic
client = Anthropic() # đọc ANTHROPIC_API_KEY từ môi trường
page_html = open("page.html", encoding="utf-8").read()
response = client.messages.create(
model="claude-haiku-4-5",
max_tokens=6000,
system="Trích xuất mọi dòng đội-mùa từ bảng này. ot_losses là null khi ô trống.",
messages=[{"role": "user", "content": page_html}],
output_config={
"format": {
"type": "json_schema",
"schema": {
"type": "object",
"properties": {
"teams": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string"},
"year": {"type": "integer"},
"wins": {"type": "integer"},
"losses": {"type": "integer"},
"ot_losses": {"type": ["integer", "null"]},
"win_pct": {"type": "number"},
"goals_for": {"type": "integer"},
"goals_against": {"type": "integer"},
},
"required": ["name", "year", "wins", "losses", "ot_losses", "win_pct", "goals_for", "goals_against"],
"additionalProperties": False,
},
}
},
"required": ["teams"],
"additionalProperties": False,
},
}
},
)
text = next(b.text for b in response.content if b.type == "text")
data = json.loads(text)
print(f"trích xuất {len(data['teams'])} đội")
output_config.format đảm bảo phản hồi sẽ được phân tích dưới dạng JSON theo schema — không có loại bỏ hàng rào, không có cách giải quyết prefill.
Không có khóa Anthropic? Chạy nó qua OpenRouter
OpenRouter cung cấp Claude thông qua giao diện hoàn thành trò chuyện tương thích với OpenAI. Đây là phiên bản mà hướng dẫn này thực hiện cho thực tế, việc truy xuất và trích xuất trong một tập lệnh tự chứa:
python
# extract_openrouter.py — cùng một quá trình trích xuất, thực hiện qua OpenRouter
import json
import os
import requests
from openai import OpenAI
resp = requests.post(
"https://api.scrapeless.com/api/v2/unlocker/request",
headers={
"Content-Type": "application/json",
"x-api-token": os.environ["SCRAPELESS_API_KEY"],
},
json={
"actor": "unlocker.webunlocker",
"input": {"url": "https://scrapethissite.com/pages/forms/", "method": "GET", "js_render": True},
},
timeout=120,
)
resp.raise_for_status()
page_html = resp.json().get("data", "")
client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=os.environ["OPENROUTER_API_KEY"])
completion = client.chat.completions.create(
model="anthropic/claude-haiku-4.5",
temperature=0,
max_tokens=6000,
response_format={"type": "json_object"},
messages=[
{
"role": "system",
"content": 'Trích xuất mọi dòng đội-mùa từ bảng này. Trả lời CHỈ với JSON: '
'{"teams":[{"name":str,"year":int,"wins":int,"losses":int,"ot_losses":int|null,'
'"win_pct":number,"goals_for":int,"goals_against":int}]}. '
"ot_losses là null khi ô trống.",
},
{"role": "user", "content": page_html},
],
)
raw = completion.choices[0].message.content.strip()
if raw.startswith("```"):
raw = raw.split("```")[1]
if raw.startswith("json"):
raw = raw[4:]
data = json.loads(raw)
print(f"trích xuất {len(data['teams'])} đội")
print(json.dumps(data["teams"][0], ensure_ascii=False))
Chạy trực tiếp đã lấy tất cả 25 đội-mùa, bản ghi đầu tiên:
text
trích xuất 25 đội
{"name": "Boston Bruins", "year": 1990, "wins": 44, "losses": 24, "ot_losses": null, "win_pct": 0.55, "goals_for": 299, "goals_against": 264}
ot_losses trở về giá trị null cho mùa giải 1990-91 — năm này trước thời điểm NHL theo dõi thống kê đó — và mỗi một trong 25 dòng đã được phân tích thành các loại đã tuyên bố mà không cần ép kiểu sau. Việc loại bỏ dấu phân cách là quan trọng ở đây: nếu không làm như vậy, json.loads sẽ thất bại ngay lập tức với đầu ra của OpenRouter của mô hình này, ngay cả khi response_format đã được thiết lập. Toàn bộ cuộc gọi: 13.365 token.
Lấy khóa API của bạn trên gói miễn phí: app.scrapeless.com
Các mẫu nâng cao
- Bảo vệ chống lại đầu ra có dấu phân cách trên luồng tổng hợp. Đường đi
output_config.formatmặc định đảm bảo JSON đúng định schema mà không có dấu phân cách; đường dẫn chat-completions của OpenRouter không đưa ra sự đảm bảo như vậy giữa các mô hình, vì vậy hãy gỡ bỏ khối```dẫn đầu trước khi phân tích bất kể bạn chuyển tiếp đến mô hình nào. - Đặt tính khả dụng là
nullmột cách rõ ràng. Schema đơn độc ("ot_losses": {"type": ["integer", "null"]}) cộng với một câu trong lời nhắc hệ thống đã tạo ra giá trị null chính xác thay vì mô hình tự động tạo ra0. Để thiếu một trong hai và kiểm tra lại trước khi tin tưởng vào trường đó. - Giữ một trang cho mỗi cuộc gọi. Ranh giới trang là ranh giới bản ghi tự nhiên; gộp nhiều trang thành một lời nhắc làm mờ đi nơi mà số liệu của đội này kết thúc và đội tiếp theo bắt đầu.
- Đo lường token trước khi mở rộng quy mô. Chạy ở trên tốn 13.365 token cho một trang 25 dòng — nhân với số trang và số trường thực tế trước khi cam kết với số lượng trang, không phải sau khi hóa đơn đến.
Giải quyết sự cố
json.loadsthất bại trên phản hồi OpenRouter ngay cả khi chế độ JSON được thiết lập. Kiểm tra xem có dấu phân cách```jsondẫn đầu và gỡ bỏ nó trước khi phân tích — lần chạy thực tế của hướng dẫn này cần chính xác biện pháp sửa chữa đó.- Một trường đáng lẽ thỉnh thoảng bị thiếu luôn trở lại là
0hoặc một chỗ đặt. Nêu rõ trong schema và lời nhắc khi một trường là có thể lànullvà điều kiện nào làm cho nó trở thành null; các mô hình mặc định sẽ lấp đầy khoảng trống trừ khi được yêu cầu không làm như vậy. - Không có hoặc chỉ một vài dòng từ một trang có hàng chục dòng. Kiểm tra số lượng HTML đã lấy đầu tiên, cách mà tập lệnh lấy ở trên làm - một lần lấy mỏng là một vấn đề về rendering hoặc truy cập, không phải điều mà lời nhắc trích xuất có thể khắc phục.
- Đầu ra thay đổi giữa các lần chạy giống nhau. Đặt
temperature=0trên đường dẫn OpenRouter; trích xuất là một công việc chép lại, và bất kỳ nhiệt độ nào trên không mời gọi sự diễn đạt lại.
Kết luận
Đường dẫn đầu ra có cấu trúc của Claude loại bỏ sự đoán già khi có được JSON đã được phân loại trở lại — output_config.format trên API gốc bỏ qua hoàn toàn các thủ thuật trước khi điền, và ngay cả bề mặt chat-completions của OpenRouter có thể lấy tất cả 25 dòng đội-mùa từ một trang thực tế với các trường được đặt null đúng, khi phản hồi được bảo vệ chống lại các dấu phân cách markdown. Những gì Claude không cung cấp là chính trang đó: một POST phía máy chủ cho mỗi trang, thông qua một lớp lấy dữ liệu được xây dựng cho rendering và truy cập, là thứ làm cho bản ghi tồn tại để trích xuất ngay từ đầu.
Sẵn sàng cung cấp cho Claude các trang thực tế?
Lớp lấy dữ liệu ở đây là Universal Scraping API — các gói và khối lượng yêu cầu có trên trang giá, với mọi tham số unlocker.webunlocker trong tài liệu dành cho nhà phát triển. Tạo một khóa trên gói miễn phí tại app.scrapeless.com và cả hai tập lệnh hoạt động như đã viết.
Câu hỏi thường gặp
Q: Claude có thể tự mình lấy dữ liệu từ trang web không?
Không. API Claude là một điểm cuối mô hình ngôn ngữ: nó trích xuất từ văn bản bạn cung cấp và không thể phát đi các yêu cầu HTTP, hiển thị JavaScript, hoặc giữ một phiên làm việc. Mọi thiết lập làm việc đều kết hợp nó với một lớp lấy dữ liệu trả về nội dung trang chính xác.
Q: Mẫu Claude nào tôi nên sử dụng cho việc trích xuất web scraping?
claude-haiku-4-5 trên API gốc (claude-haiku-4.5 trên OpenRouter) — lần chạy trực tiếp trong hướng dẫn này đã sử dụng nó và trả về tất cả 25 bản ghi với các trường được đặt null chính xác. Đây là cấp độ Claude không phải di sản rẻ nhất hiện tại; tên claude-3-haiku cũ hơn là một thế hệ trước vẫn được liệt kê nhưng không phải là khuyến nghị hiện tại.
Q: Tại sao đầu ra JSON của Claude lại không thể phân tích ngay cả khi chế độ JSON đã bật?
Trên đường dẫn tổng hợp của OpenRouter, Claude có thể bọc đầu ra của nó trong một dấu phân cách markdown ```json ngay cả khi response_format được thiết lập là json_object. Gỡ bỏ một dấu phân cách dẫn đầu trước khi gọi json.loads, hoặc sử dụng output_config.format của API gốc, cái mà đảm bảo JSON đúng định schema mà không gặp chế độ thất bại đó.
Hỏi: Claude xử lý một trường thông tin đôi khi không có trên trang nguồn như thế nào?
Một cách chính xác, khi sơ đồ và lời nhắc nói như vậy. Sơ đồ của hướng dẫn này đã đánh dấu ot_losses là ["số nguyên", "null"] và lời nhắc của hệ thống đã nêu điều kiện null; kết quả thực tế đã trả về null cho một mùa mà số liệu chưa có thay vì tạo ra một giá trị.
Hỏi: Việc lấy dữ liệu bằng Claude có hợp pháp không?
Lớp trích xuất không thay đổi các quy tắc thu thập. Chỉ thu thập các trang công khai, tôn trọng các điều khoản của trang web và các chỉ thị robots được tiêu chuẩn hóa bởi Giao thức loại trừ Robots, giữ khối lượng trong giới hạn và xử lý bất kỳ dữ liệu cá nhân nào theo luật áp dụng — cộng với các chính sách sử dụng của Anthropic về phía mô hình.
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.



