Skip to content

List input pairs results with URLs by position, so a result carries another input's data; every result is success=True #60

Description

@anil-bd

Summary

When a scraper method is given a list of URLs, the SDK pairs the returned rows with the input URLs by position. The API does not return rows in input order, so each ScrapeResult can carry one input's url and a different input's data. Every result is also marked success=True, including error rows.

On LinkedIn this attaches one person's profile to a different identity. The dataset marks those fields as personal data (pii: true), so this is a data-integrity problem, not only a wrong-label one.

The same code is in six scrapers: amazon (2 places), facebook (2), instagram, linkedin, pinterest, tiktok.

Environment

  • brightdata-sdk 2.5.2 (latest on PyPI)
  • Python 3.12.13, macOS
  • Verified 18 Sep 2026

Reproduction

from brightdata import SyncBrightDataClient

urls = [
    "https://www.linkedin.com/in/reidhoffman/",
    "https://www.linkedin.com/in/zz-not-a-real-profile-zz/",
    "https://www.linkedin.com/in/satyanadella/",
]
with SyncBrightDataClient(auto_create_zones=False) as c:
    results = c.scrape.linkedin.profiles(urls, timeout=420)

for r in results:
    d = r.data if isinstance(r.data, dict) else {}
    print(r.url, "| success", r.success, "| name", d.get("name"), "| error", d.get("error"))
https://www.linkedin.com/in/reidhoffman/              | success True | name None         | error The profile is hidden or private.
https://www.linkedin.com/in/zz-not-a-real-profile-zz/ | success True | name Reid Hoffman | error None
https://www.linkedin.com/in/satyanadella/             | success True | name Satya Nadella | error None
  • The result labelled reidhoffman holds the dead profile's error, so a caller concludes Reid Hoffman's profile is hidden or private. It is not.
  • The result labelled zz-not-a-real-profile-zz, an account that does not exist, holds Reid Hoffman's real profile.
  • All three are success=True.

The API returns a different order each run

Fetching the raw snapshot for the same three inputs, in a second job:

raw[0] reidhoffman
raw[1] satyanadella
raw[2] zz-not-a-real-profile-zz   (error row)

The first job returned the error row first. The order is not stable, so there is no input ordering a caller could choose to avoid this.

Why it happens

scrapers/linkedin/scraper.py, _scrape_urls:

results = []
for url_item, data_item in zip(url_list, result.data):
    results.append(
        ScrapeResult(
            success=True,
            data=data_item,
            url=url_item,
            ...

zip pairs the n-th input with the n-th row. success=True is a literal.

Every row already says which input it belongs to

success row: input_url = "https://www.linkedin.com/in/reidhoffman/"
error row:   input     = {"url": "https://www.linkedin.com/in/zz-not-a-real-profile-zz/"}
error row keys: ["error", "error_code", "input", "timestamp"]

So correct pairing needs no guesswork: match each row on input_url, falling back to input.url.

Other shapes of the same bug

  • If the API returns fewer rows than inputs, zip stops at the shorter list and those inputs get no result at all, with nothing to say one is missing.
  • When the job fails or times out, result.data is not a list, so the method returns one ScrapeResult instead of a list. A caller iterating the list-input return gets a single object.

Affected

grep -c "zip(url_list, result.data)" across scrapers/*/scraper.py:

scraper occurrences
amazon 2
facebook 2
instagram 1
linkedin 1
pinterest 1
tiktok 1

Expected

  1. Pair each row with its input by input_url / input.url, not by position.
  2. Set success from the row: False when the row has an error key.
  3. Return one result per input even when a row is missing, with success=False, rather than dropping it.
  4. Return a list for list input on every path, including failure and timeout.

Workaround

Ignore ScrapeResult.url on list input. Flatten every result.data and match rows to inputs on input_url or input.url yourself.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions