Quay lại blog

API Scraper Tìm Kiếm Google: Năm Thiết Lập Mặc Định Trả Về Dữ Liệu Trống

Daniel Kim
Daniel Kim

Lead Scraping Automation Engineer

20-Aug-2026

TL;DR:

  • Một 200 từ diễn viên Tìm kiếm Google không phải là bằng chứng về dữ liệu: phản hồi có thể chứa một mảng organic_results trống, một chỗ dành cho quảng cáo thay vì cho một danh sách, hoặc một trường mà được thiết kế để trống.
  • Dify tự động điền hai mặc định khóa API mà cả hai đều sản xuất 401 với {"code":14404,"message":"invalid access token"} — tên tiêu đề mặc định là Authorization và tiền tố tiêu đề mặc định là Basic, và diễn viên không chấp nhận cả hai.
  • Một quy trình n8n có thể xác thực mà không có lỗi nào và vẫn thất bại tại thời gian chạy, bởi vì sandbox nút Code trong phiên bản 2.34.4 không hiển thị bộ khởi tạo URL toàn cục.
  • Một tác nhân được cung cấp một công cụ tìm kiếm có thể trả lời mà không gọi đến nó, tạo ra văn bản trôi chảy mà không bao giờ chạm đến API; việc đếm các cuộc gọi công cụ biến cái thất bại im lặng đó thành một thất bại.
  • Các bản ghi gói địa phương trả place_id, gps_coordinatesthumbnail trống, và phone, typehours với một khoảng trắng ở phía trước — cả hai đều là hành vi đã được tài liệu, không phải lỗi cần gỡ lỗi.

Những gì diễn viên Tìm kiếm Google trả về

Diễn viên scraper.google.search nhận một truy vấn và trả về một SERP đã phân tích dưới dạng JSON. Đây là bề mặt Google của Deep SerpApi, và thường là diễn viên đầu tiên được kết nối vào một trình tạo quy trình hoặc một khung tác nhân, vì danh sách kết quả xếp hạng cung cấp cho việc theo dõi xếp hạng và nghiên cứu khách hàng một cách đồng đều.

Các lỗi dưới đây không phải là điều kỳ lạ. Chúng đến từ khoảng cách giữa một yêu cầu được chấp nhận và một tải trọng có thể sử dụng — và mỗi một trong số đó có thể được tái hiện từ bốn nền tảng chủ mà bài viết này sử dụng làm ví dụ: Dify, n8n, Activepieces và LangChain.

Yêu cầu: điểm cuối, diễn viên và tham số

Mỗi cuộc gọi là một POST đến một điểm cuối duy nhất với hai trường. actor chọn scraper và input chứa các tham số của nó:

bash Copy
curl -sS -X POST https://api.scrapeless.com/api/v1/scraper/request \
  -H 'Content-Type: application/json' \
  -H "x-api-token: $SCRAPELESS_API_KEY" \
  -d '{"actor":"scraper.google.search","input":{"q":"web scraping api"}}'

Ba tham số này bao phủ hầu hết công việc:

Tham số Mục đích
q Chuỗi truy vấn.
tbm Loại kết quả. lcl trả về gói địa phương thay vì kết quả trên web.
start Độ lệch kết quả cho phân trang — 20 mỗi trang đối với gói địa phương.

Tiêu đề xác thực là x-api-token. Tên đó là trường mà một nền tảng không mã có khả năng cao sẽ điền với mặc định của riêng nó. Quy định ngữ nghĩa HTTP cho các phản hồi 401 mong đợi một thách thức liên quan đến cơ chế xác thực của tài nguyên, vì vậy một nền tảng mà cho rằng Authorization là hợp lý — nó đơn giản chỉ đang giả định sai cơ chế dành cho điểm cuối này.

Bao bì phản hồi

Đọc bao bì trước khi bạn đọc dữ liệu. Một cuộc gọi Tìm kiếm Google thành công trả về những khóa cấp cao này:

json Copy
// illustrative sample — key shape only; values omitted
{
  "search_information": {},
  "organic_results": [],
  "related_searches": [],
  "pagination": {},
  "metadata": {}
}

Hai điều theo sau từ hình dạng đó. Không có cờ success để phân nhánh, vì vậy sự hiện diện và độ dài của organic_results là tín hiệu. Và không có khối People-Also-Ask trong bao bì này — một truy vấn cho thấy các câu hỏi liên quan trong một trình duyệt trả related_searches ở đây, vì vậy một quy trình mong đợi một mảng câu hỏi nhận None và viết một cột trống.

Với tbm được thiết lập thành lcl, kết quả chuyển sang local_results.places[] thay vì organic_results[]. Một quy trình mà mã hóa cứng một con đường im lặng sản xuất không có gì khi con đường khác được yêu cầu.

Đọc phản hồi trong mã

Khẳng định sau yêu cầu là phần đáng sao chép. Ví dụ này nâng lên thay vì trả về một danh sách trống, vì vậy một thất bại nổi lên nơi nó xảy ra thay vì ba bước sau đó trong một bảng tính:

python Copy
import json
import os
import urllib.request

ENDPOINT = "https://api.scrapeless.com/api/v1/scraper/request"


def search(query: str) -> dict:
    payload = json.dumps({"actor": "scraper.google.search", "input": {"q": query}}).encode()
    request = urllib.request.Request(
        ENDPOINT,
        data=payload,
        headers={
            "Content-Type": "application/json",
            "x-api-token": os.environ["SCRAPELESS_API_KEY"],
        },
    )
    # urlopen raises HTTPError on any 4xx or 5xx, so a rejected call never reaches the parser.
    with urllib.request.urlopen(request, timeout=120) as response:
        return json.loads(response.read())


serp = search("web scraping api")
organic = serp.get("organic_results") or []

if not organic:
    raise SystemExit(f"no organic_results in the response; envelope was {sorted(serp)}")

print(f"organic_results: {len(organic)}")
print(f"first result: {organic[0]['title']}")
print(f"envelope keys: {sorted(serp)}")

Vắng mặt và trống là những trạng thái khác nhau, và Quy định định dạng trao đổi JSON không giúp bạn phân biệt "khóa đã bị bỏ qua" với "giá trị là một chuỗi rỗng". Quyết định cái nào mà quy trình của bạn xem như một lỗi trước khi bạn viết lần chèn đầu tiên.

Làm việc qua điều này trên kế hoạch miễn phí là đủ để thấy mọi hành vi được mô tả ở đây — tạo một tài khoản Scrapeless và sử dụng cùng một khóa trên tất cả bốn nền tảng bên dưới.

Năm mặc định trả về dữ liệu trống

Tiêu đề khóa API mà nền tảng của bạn tự động điền là sai

Trong Dify 1.16.1, việc nhập một sơ đồ OpenAPI như một công cụ tùy chỉnh và chọn xác thực API Key để lại hai trường ở mặc định mà diễn viên từ chối. Tên tiêu đề mặc định là Authorization, và tiền tố tiêu đề mặc định là Basic — cái này gửi x-api-token: Basic <key> ngay cả sau khi bạn sửa tên. Cả hai đều sản xuất cùng một phản hồi:

json Copy
{ "code": 14404, "message": "invalid access token" }

Một thông báo lỗi, hai nguyên nhân độc lập, đó chính là nguyên nhân khiến việc chẩn đoán trở nên tốn kém. Các tên cấu hình làm việc đều liệt kê cả ba:

Trường Giá trị
Loại xác thực API Key
Tên tiêu đề x-api-token
Tiền tố tiêu đề Custom

Dify cũng làm phẳng một đối tượng body yêu cầu lồng vào một tham số chuỗi, vì vậy trường input được truyền đến dưới dạng văn bản thay vì một đối tượng có cấu trúc. Cả một đối tượng và một chuỗi JSON đều được chấp nhận, đó là lý do tại sao trường hợp này hiếm khi được chú ý cho đến khi một nút phía dưới cố gắng đọc input.q.

Một quy trình làm việc có thể xác thực vẫn có thể thất bại tại thời gian thực

Xác thực tĩnh và thực thi không đồng ý trong nút mã n8n. Một quy trình làm việc sử dụng new URL(link).hostname để nhóm kết quả theo miền xác thực mà không có lỗi nào, sau đó thất bại đối với mục đầu tiên với URL is not defined. Môi trường thử nghiệm trong phiên bản 2.34.4 không tiết lộ toàn cầu đó, mặc dù Tiêu chuẩn URL WHATWG định nghĩa nó như một trình khởi tạo Web API và báo cáo về nút mã thất bại mà không có trình khởi tạo URL của n8n ghi lại triệu chứng.

Rút ra tên miền với các thao tác chuỗi thay vào đó:

javascript Copy
// The Code node sandbox does not expose the global URL constructor,
// so the hostname comes from string operations.
const hostname = (link) =>
  link ? link.replace(/^[a-z]+:\/\//i, '').replace(/^www\./i, '').split(/[/?#]/)[0] : '';

const results = [
  { position: 1, link: 'https://www.scrapeless.com/vi/product/deep-serp-api' },
  { position: 2, link: 'https://docs.scrapeless.com/en/deep-serp-api/quickstart/introduction/' },
];

for (const result of results) {
  console.log(result.position, hostname(result.link));
}

Xác thực trong trình xây dựng quy trình làm việc kiểm tra đồ thị, không phải mã bên trong một nút. Do đó, một dấu kiểm màu xanh không nói lên điều gì về việc một nút mã có thực thi hay không.

Tham chiếu bước mà không giải quyết được gì

Trong Activepieces 0.82.0, JSON đã phân tích của một bước HTTP sống dưới body. Tham chiếu là {{step_1.body.organic_results}}, và {{step_1.organic_results}} không giải quyết được gì cả — không có lỗi, không có cảnh báo, chỉ một vòng lặp trống và một lần chạy báo cáo thành công. Với tbm được đặt thành lcl, đường dẫn là {{step_1.body.local_results.places}}.

Một lỗi tham chiếu thiếu trông giống hệt như một tập kết quả thực sự trống, vì vậy hãy kiểm tra đường dẫn tham chiếu trước khi bạn bắt đầu tìm kiếm vấn đề dữ liệu.

Đại lý trả lời mà không gọi công cụ

Cho một đại lý một công cụ tìm kiếm và nó có thể không sử dụng nó. Một mô hình nhỏ được giao cả công cụ tìm kiếm và công cụ lấy dữ liệu thường chạy tìm kiếm, sau đó trả lời từ các đoạn kết quả trong khi mô tả về những gì "trang đang nói" — chưa bao giờ mở trang. Văn phong trôi chảy và trích dẫn được ngụ ý, vì vậy không có gì trong đầu ra đánh dấu câu trả lời là không có cơ sở.

Giải pháp là một khẳng định, không phải một lời nhắc tốt hơn. Đếm số lần gọi công cụ và coi không có lần nào như một thất bại:

Chú ý: đoạn mã này gói gọn một đại lý hiện có, vì vậy việc chạy nó yêu cầu một đại lý LangChain được xây dựng và một khóa nhà cung cấp mô hình. Mọi thứ mà nó phụ thuộc vào đều là đầu ra tiêu chuẩn agent.stream(...).

python Copy
tool_calls = 0
for chunk in agent.stream({"messages": [("human", question)]}, stream_mode="values"):
    message = chunk["messages"][-1]
    tool_calls += len(getattr(message, "tool_calls", None) or [])

if tool_calls == 0:
    raise SystemExit("the model answered without calling a tool; the answer is not grounded")

Hướng dẫn một công cụ duy nhất là đáng tin cậy trên các mô hình nhỏ. Các hướng dẫn chuỗi — tìm kiếm, sau đó lấy kết quả hàng đầu — là nơi cuộc gọi công cụ âm thầm biến mất, vì vậy hãy tách các bước trong mã và để mô hình xử lý một cuộc gọi tại một thời điểm.

Các trường được để trống có chủ đích

Một số giá trị trống là chính xác. Trong các kết quả gói địa phương, place_id, gps_coordinates, và thumbnail trả về trống, và phone, type, và hours đến với một khoảng trắng ở đầu. Cả hai đều không phải là lỗi, và cả hai đều phá vỡ mã ngây thơ: một sự không khớp khoảng trắng cuối sẽ biến một khóa loại bỏ trùng lặp thành một bản sao, và coi place_id trống như một lỗi sẽ khiến bạn phải gỡ lỗi hành vi đang hoạt động như tài liệu.

Chuẩn hóa khi vào:

Trường Hành vi Xử lý
phone, type, hours Khoảng trắng ở đầu Cắt trước khi lưu trữ hoặc so sánh.
place_id, gps_coordinates, thumbnail Trống trên các kết quả địa phương Xem như có thể có giá trị null; không khóa bản ghi theo chúng.
organic_results vs local_results.places Phụ thuộc vào tbm Chọn đường dẫn từ yêu cầu, không phải bằng cách đoán.

Kỷ luật tương tự cũng áp dụng cho việc đếm. Một mảng kết quả có thể chứa các vị trí tài trợ và các chỗ trống bố cục bên cạnh danh sách, vì vậy độ dài của mảng không phải là số lượng kết quả — hãy lọc theo trường loại bản ghi của chính nó trước khi bạn báo cáo một số, hoặc mọi số phía dưới đều thừa hưởng mọi tải quảng cáo mà trang đã phục vụ.

Kết luận

Các lỗi tốn kém trong một thiết lập thu thập thông tin không mã kết thúc xanh mà không có gì trong đó: một tiêu đề xác thực mà nền tảng của bạn đã điền sẵn, một toàn cầu Web API mà môi trường thử nghiệm bỏ qua, một đường dẫn tham chiếu thiếu một đoạn, một đại lý đã bỏ qua công cụ, hoặc một trường mà luôn luôn sẽ trống. Mỗi cái đều có một bản sửa lỗi một dòng và không có thông báo lỗi nào chỉ ra nó.
Hai thói quen bắt tất cả năm. Đọc phong bì phản hồi trước dữ liệu, và khẳng định những gì bạn mong đợi — một mảng không rỗng, một cuộc gọi công cụ, một loại bản ghi — để một lỗi im lặng trở thành một thất bại ồn ào tại bước đã gây ra nó. Hướng dẫn quy trình làm việc quét n8ncách tích hợp LangChain cho thấy cùng một tác nhân được kết nối từ đầu đến cuối khi các kiểm tra đó được thực hiện.

Sẵn sàng để xây dựng trên một bề mặt SERP trả về một phong bì đã được tài liệu hóa? Kiểm tra tài liệu Deep SerpApi để biết toàn bộ tập hợp tham số, xem lại các kế hoạch và khối lượng bao gồm, và bắt đầu với gói miễn phí.

Câu hỏi thường gặp

H: Tại sao tác nhân Tìm kiếm Google của tôi lại trả về 200 với một mảng organic_results rỗng?

Một mảng organic_results rỗng với một 200 nghĩa là yêu cầu đã được chấp nhận và phân tích nhưng không tạo ra kết quả web cho hình dạng truy vấn đó. Kiểm tra ba điều theo thứ tự: xem tbm có được thiết lập thành lcl hay không, điều này di chuyển kết quả về local_results.places[]; xem truy vấn tự nó có ý định kết quả hay không; và xem nền tảng của bạn có đang đọc thân thiện đã được phân tích thay vì phong bì hay không. Không có cờ success nào trong phản hồi, vì vậy độ dài của mảng là tín hiệu duy nhất.

H: Điều gì gây ra {"code":14404,"message":"invalid access token"} khi khóa là đúng?

Phản hồi đó có nghĩa là khóa chưa bao giờ đến theo hình thức mà điểm cuối mong đợi. Tiêu đề phải là x-api-token mang theo khóa chính. Các nền tảng mặc định là Authorization, hoặc thêm Basic hoặc Bearer vào giá trị, gửi một tiêu đề mà điểm cuối không thể đọc được — và thông điệp là giống nhau trong mọi trường hợp, vì vậy hãy xác minh tên tiêu đề và bất kỳ cài đặt tiền tố nào một cách riêng biệt.

H: Tại sao nút Code của tôi trong n8n lại thất bại với URL is not defined khi quy trình làm việc xác thực?

Khu vực cát của nút Code trong n8n 2.34.4 không hiển thị toàn cầu URL constructor, và xác thực quy trình làm việc không thực hiện mã nút, vì vậy đồ thị vượt qua các kiểm tra của nó và lần chạy thất bại ở mục đầu tiên. Phân tích tên miền với các thao tác chuỗi, hoặc di chuyển việc xử lý URL vào một nút cung cấp API.

H: Làm thế nào tôi có thể biết liệu một tác nhân thực sự đã sử dụng công cụ tìm kiếm?

Đếm số lần gọi công cụ trong các tin nhắn đã phát trực tiếp và thất bại khi số đếm bằng không. Một mô hình có thể tạo ra một câu trả lời hoàn chỉnh, tự tin mà không cần gọi bất kỳ công cụ nào, và không có gì trong văn bản phân biệt điều đó với một câu trả lời có cơ sở. Xem số lần gọi công cụ là yêu cầu bắt buộc hơn là kiểm tra văn bản.

H: Có phải giá trị place_idgps_coordinates rỗng là một lỗi không?

Không. Các bản ghi local-pack trả về place_id, gps_coordinates, và thumbnail rỗng, vì vậy các trường đó có thể nullable theo thiết kế. Giữ bản ghi và điền vị trí từ các trường hiện có thay vì loại bỏ hàng hoặc thêm xử lý lỗi xung quanh hành vi đã được mong đợi.

H: Tại sao vòng lặp Activepieces của tôi lặp lại không lần nào khi bước HTTP thành công?

Phản hồi đã phân tích nằm dưới body, vì vậy {{step_1.organic_results}} không giải quyết thành gì trong khi {{step_1.body.organic_results}} giải quyết thành mảng. Một tham chiếu bị thiếu không tạo ra lỗi trong Activepieces 0.82.0 — vòng lặp chỉ nhận được không gì và lần chạy vẫn báo thành công, điều này khiến nó không thể phân biệt được với một tập kết quả rỗng cho đến khi bạn kiểm tra đường dẫ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.

Bài viết phổ biến nhất

Danh mục