{"id":4393,"date":"2026-08-18T04:47:31","date_gmt":"2026-08-18T04:47:31","guid":{"rendered":"https:\/\/tucumandevelopers.com\/index.php\/2026\/08\/18\/the-day-our-scraper-fleet-shipped-nothing-and-the-afternoon-it-shipped-three\/"},"modified":"2026-08-18T04:47:31","modified_gmt":"2026-08-18T04:47:31","slug":"the-day-our-scraper-fleet-shipped-nothing-and-the-afternoon-it-shipped-three","status":"publish","type":"post","link":"https:\/\/tucumandevelopers.com\/index.php\/2026\/08\/18\/the-day-our-scraper-fleet-shipped-nothing-and-the-afternoon-it-shipped-three\/","title":{"rendered":"The day our scraper fleet shipped nothing \u2014 and the afternoon it shipped three"},"content":{"rendered":"<div>\n<div><\/div>\n<p>What made it expensive was that the failure was invisible from inside the process. Every stage worked. The specs were fine, the tests were green, the pricing wired up correctly, the publisher refused correctly. The lane was healthy and the output was zero, which is the worst combination to debug because nothing is flashing red.<\/p>\n<h2> <a name=\"the-afternoon-that-shipped-three\" href=\"#the-afternoon-that-shipped-three\"> <\/a> The afternoon that shipped three <\/h2>\n<p>We changed one variable: target class. Instead of marketplaces, we took three public research and regulatory APIs that require no key. Scaffold, implement, cloud QA, publish. Roughly an hour each, all three passed cloud QA on the first run.<\/p>\n<p>There is no clever trick here. A public JSON API has no incentive to fingerprint you, no CAPTCHA, no TLS fingerprint check, no HTML that gets rewritten on Tuesday. The whole class of failure that eats marketplace scrapers simply does not exist. What is left is ordinary engineering: paging, schema, and error handling.<\/p>\n<h2> <a name=\"why-is-openalex-a-good-scraping-target\" href=\"#why-is-openalex-a-good-scraping-target\"> <\/a> Why is OpenAlex a good scraping target? <\/h2>\n<p>OpenAlex is the open replacement for Microsoft Academic Graph \u2014 roughly 250 million scholarly works, with authors, institutions, journals, citation counts and open-access status. It is free, it needs no API key, and it is genuinely well-behaved if you are.<\/p>\n<p>Two things matter in practice.<\/p>\n<p><strong>Use cursor paging, not offset.<\/strong> Offset paging on a corpus this size falls apart past the first few thousand rows \u2014 the API caps how deep you can page and the cost of a deep offset grows. The cursor is the supported path: <\/p>\n<div>\n<pre><code><span>GET https:\/\/api.openalex.org\/works?per-page=200&amp;cursor=* <\/span><\/code><\/pre>\n<div>\n<\/p><\/div>\n<\/p><\/div>\n<p>Each response hands you <code>meta.next_cursor<\/code>; you pass it back until it comes back null. Stateless, stable under concurrent updates, and it does not degrade at depth.<\/p>\n<p><strong>Identify yourself and get the fast lane.<\/strong> OpenAlex runs a &#8220;polite pool&#8221; \u2014 include a contact in your <code>User-Agent<\/code> or a <code>mailto<\/code> parameter and your requests go to a separate, faster, more reliably-served pool. This is the rare case where telling the server who you are makes your scraper <em>better<\/em>, not more blockable. We send our public contact URL, never a personal address.<\/p>\n<h2> <a name=\"the-trap-that-survived-the-easy-target\" href=\"#the-trap-that-survived-the-easy-target\"> <\/a> The trap that survived the easy target <\/h2>\n<p>Keyless does not mean trivial. The bug that cost us the most time on this batch had nothing to do with blocking.<\/p>\n<p>Apify Actors can declare a dataset schema, and ours declared fields like this: <\/p>\n<div>\n<pre><code><span>{<\/span><span> <\/span><span>\"doi\"<\/span><span>:<\/span><span> <\/span><span>{<\/span><span> <\/span><span>\"type\"<\/span><span>:<\/span><span> <\/span><span>\"string\"<\/span><span> <\/span><span>}<\/span><span> <\/span><span>}<\/span><span> <\/span><\/code><\/pre>\n<div>\n<\/p><\/div>\n<\/p><\/div>\n<p>That is wrong, and it is wrong in the worst possible way: it only fails on a <em>sparse<\/em> record. Scholarly metadata is extremely sparse \u2014 plenty of works have no DOI, no abstract, no ISSN, no funder. The first record missing one of those fields produces a null, the null fails validation, and <code>push_data<\/code> dies mid-run. A fully-populated QA sample never triggers it, so the smoke test passes and the customer&#8217;s real query is what breaks.<\/p>\n<p>The fix is one character of JSON per field: <\/p>\n<div>\n<pre><code><span>{<\/span><span> <\/span><span>\"doi\"<\/span><span>:<\/span><span> <\/span><span>{<\/span><span> <\/span><span>\"type\"<\/span><span>:<\/span><span> <\/span><span>[<\/span><span>\"string\"<\/span><span>,<\/span><span> <\/span><span>\"null\"<\/span><span>]<\/span><span> <\/span><span>}<\/span><span> <\/span><span>}<\/span><span> <\/span><\/code><\/pre>\n<div>\n<\/p><\/div>\n<\/p><\/div>\n<p>We fixed it at the scaffolder so no future Actor can be born with it. The general lesson: <strong>a QA fixture that is too clean is worse than no fixture<\/strong>, because it converts a loud failure into a delayed one.<\/p>\n<h2> <a name=\"what-this-means-for-your-backlog\" href=\"#what-this-means-for-your-backlog\"> <\/a> What this means for your backlog <\/h2>\n<p>Sort by class first:<\/p>\n<ol>\n<li> <strong>Keyless public APIs<\/strong> \u2014 ship first-attempt. Government registries, research indexes, open data portals, standards bodies. Low glamour, real buyers, and they do not fight back.<\/li>\n<li> <strong>Plain HTML sites with no commercial anti-bot<\/strong> \u2014 ship with ordinary care.<\/li>\n<li> <strong>Commercially defended marketplaces<\/strong> \u2014 only with a specific, tested plan for their defences, and a named tier you have actually probed. Never on optimism.<\/li>\n<\/ol>\n<p>We ran category 3 on optimism for a long time. The scoreboard was honest about it exactly once, on the day it produced a zero.<\/p>\n<h2> <a name=\"what-the-scraper-actually-does\" href=\"#what-the-scraper-actually-does\"> <\/a> What the scraper actually does <\/h2>\n<p><a href=\"https:\/\/apify.com\/DevilScrapes\/openalex-works-scraper\" target=\"_blank\" rel=\"noopener noreferrer\">OpenAlex Works Scraper<\/a> searches the 250M-work catalogue and returns one flat row per work \u2014 title, DOI, authors with ORCIDs and affiliations, journal, publication date, citation count and open-access status \u2014 as JSON, CSV or Excel. It is pay-per-result at <strong>$2.05 per 1,000 rows<\/strong>, so a query that returns nothing costs you nothing beyond the start fee.<\/p>\n<p>We are not going to pretend that any source stays static forever. APIs deprecate fields, tighten rate limits and reshape their pagination, and when OpenAlex does, absorbing that is our job, not yours \u2014 the cursor handling, the retries and the schema drift are on our side of the line.<\/p>\n<p>If you work the same corpus from other angles, the neighbours are <a href=\"https:\/\/apify.com\/DevilScrapes\/pubmed-papers-scraper\" target=\"_blank\" rel=\"noopener noreferrer\">Crossref-adjacent research tooling<\/a>, <a href=\"https:\/\/apify.com\/DevilScrapes\/arxiv-papers-scraper\" target=\"_blank\" rel=\"noopener noreferrer\">arXiv<\/a> and <a href=\"https:\/\/apify.com\/DevilScrapes\/eu-cordis-grants\" target=\"_blank\" rel=\"noopener noreferrer\">EU CORDIS grants<\/a>.<\/p>\n<h2> <a name=\"the-rule-we-wrote-down\" href=\"#the-rule-we-wrote-down\"> <\/a> The rule we wrote down <\/h2>\n<blockquote>\n<p>A backlog sorted only by revenue potential will happily spend a month on targets that cannot ship. Sort by target class first, then by revenue inside each class.<\/p>\n<\/blockquote>\n<p>That is the whole finding. It cost us a zero-release day to learn, and it is the cheapest thing in this post to copy.<\/p>\n<\/p><\/div>\n<\/div>\n<\/div>\n<\/div>\n<p>Fuente: <a href=\"https:\/\/dev.to\/devil_scrapes\/the-day-our-scraper-fleet-shipped-nothing-and-the-afternoon-it-shipped-three-4jjo\">Art\u00edculo original<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>What made it expensive was that the failure was invisible from inside the process. Every stage worked. The specs were fine, the tests were green, the pricing wired up correctly, the publisher refused correctly. The lane was healthy and the output was zero, which is the worst combination to debug because nothing is flashing red. [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":4392,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"webixso_pending_account_ids":""},"categories":[41],"tags":[],"class_list":["post-4393","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devto"],"jetpack_publicize_connections":[],"_links":{"self":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/posts\/4393","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/comments?post=4393"}],"version-history":[{"count":0,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/posts\/4393\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/media\/4392"}],"wp:attachment":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/media?parent=4393"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/categories?post=4393"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/tags?post=4393"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}