<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Noorsimar Singh</title>
    <description>The latest articles on DEV Community by Noorsimar Singh (@noorsimar).</description>
    <link>https://dev.to/noorsimar</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2663013%2F85a3ee51-54ff-4784-b13e-71865066541a.jpg</url>
      <title>DEV Community: Noorsimar Singh</title>
      <link>https://dev.to/noorsimar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/noorsimar"/>
    <language>en</language>
    <item>
      <title>Proxy Routing for Web Scraping &amp; Vibe Scraping in 2026: Best Proxies for Web Scraping (and Why Your Scraping Proxy Won’t Survive Prod)</title>
      <dc:creator>Noorsimar Singh</dc:creator>
      <pubDate>Sun, 12 Jul 2026 06:31:45 +0000</pubDate>
      <link>https://dev.to/noorsimar/proxy-routing-for-web-scraping-vibe-scraping-in-2026-best-proxies-for-web-scraping-and-why-your-1gni</link>
      <guid>https://dev.to/noorsimar/proxy-routing-for-web-scraping-vibe-scraping-in-2026-best-proxies-for-web-scraping-and-why-your-1gni</guid>
      <description>&lt;p&gt;If you have a web scraper that "works" locally and then collapses the moment you put it on a schedule, you are not alone. Most vibe scraping failures are not about selectors. They are about proxy choices, inconsistent web scraping proxies, and a proxy for web scraping setup that has zero opinion about risk, geo, sessions, or recovery. In other words: your IP strategy is the product, and your IP address is the first thing anti-bot systems score.&lt;/p&gt;

&lt;p&gt;This guide is about proxy routing for web scraping. Not the generic "best proxies for web scraping" listicle stuff. Real routing: picking the right proxy provider, proxy type, proxy pool strategy, rotation strategy, and browser fingerprint profile per request.. then watching it like a dependency so you can fix it before your on-call gets spicy. If you came here looking for the best proxy or even the best proxy for web, the truth is you only get that answer by testing a real scraping proxy against your specific web pages.&lt;/p&gt;

&lt;h2&gt;Proxy for web scraping: the real problem is "proxy rotation" is not routing&lt;/h2&gt;

&lt;p&gt;Proxy rotation is a timer. Every N requests, swap IP address. It feels productive because you see new IPs in logs and you stop getting blocked for a few minutes.&lt;/p&gt;

&lt;p&gt;Proxy routing is a policy. For each web request you decide: which proxy service, which geo, which ASN/ISP profile, whether you need sticky sessions for scraping, whether a browser is required, and what the fallback ladder looks like when you hit 403, 429, timeouts, or CAPTCHAs. Good routing also decides &lt;em&gt;how&lt;/em&gt; to route scraping (HTTP vs browser vs API) and when to change proxy type, not just which IP you got.&lt;/p&gt;

&lt;p&gt;Vibe web scraping breaks in production for a boring reason: it is optimized for "I got the data once," not SLOs, not cost, and definitely not incident recovery. Anti-bot systems learn patterns fast. If your proxy routing logic does not learn too, you just keep buying proxies and calling it scale.&lt;/p&gt;

&lt;h2&gt;Vibe scraping: what it looks like in the wild (and why it fails)&lt;/h2&gt;

&lt;h3&gt;Symptoms you’ll recognize&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;403/429 rates spike right after a deploy, and nobody can reproduce it on their laptop.&lt;/li&gt;
  &lt;li&gt;CAPTCHAs show up after 5 to 10 minutes, not on the first request. That delay is a clue.&lt;/li&gt;
  &lt;li&gt;Sessions die mid-pagination, mid-checkout, or mid-login because your IP changes mid-flow (your IP address changed, but the cookies didn’t).&lt;/li&gt;
  &lt;li&gt;"Same code, different day" failures. You change nothing, but success rate tanks.&lt;/li&gt;
  &lt;li&gt;Proxy spend increases while throughput drops because retries silently double your request volume.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Root causes (anti-bot reality, not theory)&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;IP reputation is target-specific.&lt;/strong&gt; The proxy network that works on one e-commerce site can be useless on another. One IP address can be "clean" for one target and burned for another.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;ASN and ISP quality beat marketing labels.&lt;/strong&gt; "Residential proxies" is a broad bucket. Some subnets are basically pre-burned. The same is true for datacenter proxies.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Session continuity matters.&lt;/strong&gt; If the site ties cookies, TLS fingerprints, and behavior to an IP, naive proxy rotation breaks the chain.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Browser fingerprint and proxy alignment is real.&lt;/strong&gt; A datacenter IP paired with a very human browser profile can look weirder than a bot.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want a quick gut-check from people who deal with this daily, the &lt;a href="https://www.reddit.com/r/WebScrapingInsider/comments/1ujigef/web_scraping_insider_8_ethical_residential_proxy/" rel="noopener noreferrer"&gt;ethical residential proxy sourcing discussion&lt;/a&gt; is a useful thread. Not for vendor names. For the reality check on what "clean" IPs actually means (and why "best proxy" claims are often meaningless without context).&lt;/p&gt;

&lt;h2&gt;Using proxies for web scraping: the production definition of proxy routing&lt;/h2&gt;

&lt;h3&gt;Routing decisions you should be making per request&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;Target domain policy:&lt;/strong&gt; amazon.com is not walmart.com. Even different paths on the same host behave differently.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Geo requirement:&lt;/strong&gt; country, region, sometimes city. Then validate it, because "US" is not always US.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Proxy type:&lt;/strong&gt; datacenter proxies, ISP proxies, residential proxies, mobile proxies (different proxy types exist for a reason).&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Session strategy:&lt;/strong&gt; sticky sessions for scraping vs rotating proxies per request (and the right proxy rotation rules for each route).&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Budget strategy:&lt;/strong&gt; cheap-first for low-risk pages, success-first for high-value flows.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Fallback ladder:&lt;/strong&gt; provider A to provider B to provider C, with explicit stop rules.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Browser mode:&lt;/strong&gt; raw HTTP (via http proxies / https proxies), headless browser, or a rendering/scraping API.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;The minimal routing metadata to log (non-negotiable)&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;target host and a path class (product page, search, category, login)&lt;/li&gt;
  &lt;li&gt;proxy provider and plan/config (residential and datacenter proxies, geo, session id)&lt;/li&gt;
  &lt;li&gt;egress IP and IP address (log what you actually used, not what you requested)&lt;/li&gt;
  &lt;li&gt;status code family plus block markers (captcha keywords, JS challenge hints)&lt;/li&gt;
  &lt;li&gt;latency percentiles per route (p50 and p95) and response size&lt;/li&gt;
  &lt;li&gt;retry count and retry reason&lt;/li&gt;
  &lt;li&gt;cost estimate per successful page (even if it’s rough)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Action step: add a request_id and propagate it through scraper logs, proxy logs, and your parsing output. If you cannot trace a single request across the pipeline, your debugging loop is going to be slow and expensive.. especially once you use proxies across multiple providers.&lt;/p&gt;

&lt;p&gt;On teams that do this at scale, a monitoring layer helps. You can roll your own, but platforms like &lt;a href="https://scrapeops.io/monitoring-scheduling/?fpr=noorsimar" rel="noopener noreferrer"&gt;monitor your scrapers&lt;/a&gt; give you the "scraper health dashboard" view that many teams forget to build until after the third incident.&lt;/p&gt;

&lt;h2&gt;Proxy types for web scraping: a practical routing policy (start with a decision tree, then automate)&lt;/h2&gt;

&lt;h3&gt;Decision tree (baseline)&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;Public, low-risk pages:&lt;/strong&gt; start with datacenter proxies. Rotate per request. Keep concurrency high, but watch 429s.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Authenticated or session-heavy flows:&lt;/strong&gt; use sticky sessions (ISP or residential). Limit concurrency per session.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Geo-sensitive content:&lt;/strong&gt; enforce geo-targeted proxy rotation and track which regions actually work (verify the IP address periodically).&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;When blocks spike:&lt;/strong&gt; escalate to residential proxies or mobile proxies, then add browser rendering if needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;Example policy in pseudo-config (tool-agnostic)&lt;/h4&gt;

&lt;p&gt;Keep this as config, not code. That one decision alone saves you during incidents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;routes.yaml&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;example.com/search&lt;/strong&gt;: primary datacenter_pool (rotate per request, 2 retries) then fallback residential_pool US (sticky 10m, 2 retries) then rendering_api / web scraping API (1 retry)&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;example.com/pdp&lt;/strong&gt;: primary isp_pool US (sticky 30m, 3 retries) then fallback residential_pool (sticky 30m)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;Where teams usually get it wrong&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;One global proxy pool for all targets. Everything shares the same failure domain and the same IP reputation bleed.&lt;/li&gt;
  &lt;li&gt;No separation between "cheap crawl" and "expensive extraction." Your costs balloon quietly.&lt;/li&gt;
  &lt;li&gt;No feedback loop. A route keeps failing, but the system keeps sending traffic into it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to centralize routes without binding yourself to one proxy vendor, using a proxy aggregator pattern is a clean approach. Some teams build it in-house. Others use something like the &lt;a href="https://scrapeops.io/proxy-aggregator/" rel="noopener noreferrer"&gt;proxy aggregator&lt;/a&gt; so they can "combine all proxies into one api" and focus on policy, not plumbing. Either way, your proxy infrastructure should make provider swaps easy.&lt;/p&gt;

&lt;h2&gt;Scraping with proxies: dynamic proxy routing needs a feedback loop (not a static rule set)&lt;/h2&gt;

&lt;p&gt;Static rules are better than vibes. But dynamic proxy routing is where reliability shows up. You are basically doing traffic engineering, except the upstream is a hostile website and the downstream is your SLA. This is what proxies for scraping looks like when you’re scraping at scale.&lt;/p&gt;

&lt;h3&gt;Signals you should route on (with concrete thresholds)&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;403 rate&lt;/strong&gt; over last 5 minutes &amp;gt; 3%: escalate proxy type or provider, and reduce concurrency.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;429 spike:&lt;/strong&gt; add jitter, slow down, and move to ISP/residential if the site rate-limits by IP address.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;CAPTCHA detection&lt;/strong&gt; &amp;gt; 1%: switch to a browser-based route or a rendering-capable scraping API (aka a web scraping api route).&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;p95 latency&lt;/strong&gt; &amp;gt; 8s: quarantine that provider+geo combination temporarily.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Quarantine logic (simple, effective)&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;If provider+geo fails 5 times in a row, quarantine for 15 minutes.&lt;/li&gt;
  &lt;li&gt;Let it recover early if you see 2 consecutive successes during probe traffic.&lt;/li&gt;
  &lt;li&gt;Keep a known-good emergency route, even if it is expensive. Calm ops beats cheap ops.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice, provider failover is easier if your request code hits one endpoint and you change routing behind it. ScrapeOps offers both a Proxy API Aggregator and a residential proxy aggregator, and the "single integration, multiple providers" idea is the point. You can do the same with other stacks, but the pattern matters more than the vendor.&lt;/p&gt;

&lt;h2&gt;Proxy pool, rotating proxies, and geo: don’t just pick a country.. pick the right network shape&lt;/h2&gt;

&lt;h3&gt;What "geo" really means operationally&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Country is not region. Region is not city.&lt;/li&gt;
  &lt;li&gt;Some targets care about ASN or ISP more than country.&lt;/li&gt;
  &lt;li&gt;Localized SERPs and e-commerce pricing can vary by IP geolocation, timezone, language headers, currency, and login state.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Action steps: harden geo routing&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Maintain a per-target geo matrix: allowed geos plus observed success rate.&lt;/li&gt;
  &lt;li&gt;Validate geo with an IP-check endpoint periodically. Do not trust "US" labels blindly; verify the egress IP and IP address match your expectation.&lt;/li&gt;
  &lt;li&gt;Keep separate proxy pool buckets: US-any, US-CA, EU-DE, APAC-SG, and so on. This becomes your web scraping proxy pool for different routes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;Linkable artifact: a geo matrix template&lt;/h4&gt;

&lt;p&gt;Copy this table into your runbook or wiki and actually fill it in. You will thank yourself later.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;Columns:&lt;/strong&gt; Target, Page Type, Required Geo, Preferred ASN/ISP, Proxy Type, Sticky?, Notes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are shopping for geo coverage, it helps to review how different providers describe their networks. For example, &lt;a href="https://brightdata.com/proxy-types/residential-proxies" rel="noopener noreferrer"&gt;Bright Data&lt;/a&gt; publishes details about residential routing and coverage. Even if you do not buy, the documentation helps you ask better questions when choosing a web scraping proxy.&lt;/p&gt;

&lt;h2&gt;Proxy type deep dive: residential proxies vs datacenter proxies (the non-marketing version)&lt;/h2&gt;

&lt;p&gt;There is no universal best proxy. There is only "best proxy for web" for a specific target, page type, and traffic shape. That’s why proxy routing matters, and why different proxy types matter for different scraping tasks.&lt;/p&gt;

&lt;h3&gt;Datacenter proxies: when they’re the right tool&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;High-volume scraping where a small failure rate is acceptable.&lt;/li&gt;
  &lt;li&gt;Public pages with weak bot protection.&lt;/li&gt;
  &lt;li&gt;Cost-sensitive scraping projects where retries are controlled.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Residential proxies: when you actually need them&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Targets with aggressive anti-bot scoring tied to IP reputation.&lt;/li&gt;
  &lt;li&gt;Price localization, inventory checks, or content that shifts by region.&lt;/li&gt;
  &lt;li&gt;Sites that block whole datacenter ranges fast.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;ISP proxies: the boring but stable middle ground&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Often more stable for sessions than rotating residential proxies.&lt;/li&gt;
  &lt;li&gt;Good for login flows, long pagination, and anything that breaks when IP changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Mobile proxies: expensive, situational&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Use as a last resort or for mobile-only endpoints.&lt;/li&gt;
  &lt;li&gt;Only if your browser fingerprint and proxy alignment is tight.&lt;/li&gt;
  &lt;li&gt;Remember: mobile proxies route requests through carrier networks, so expect different latency and different block dynamics.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;People love asking about cheap options. The problem is that cheap often means recycled IPs. If you want a reality check, the &lt;a href="https://www.reddit.com/r/WebScrapingInsider/comments/1rro4bt/what_are_some_best_cheap_residential_proxies/" rel="noopener noreferrer"&gt;cheap residential proxies discussion&lt;/a&gt; is a good reminder that "cheap" and "reliable" rarely land in the same place for difficult targets. Be extra skeptical of free proxies for anything beyond trivial testing.&lt;/p&gt;

&lt;h2&gt;Proxy for web scraping sessions: sticky sessions (how to stop breaking workflows mid-flow)&lt;/h2&gt;

&lt;h3&gt;What sticky sessions protect&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Cart and session cookies tied to an IP address.&lt;/li&gt;
  &lt;li&gt;Anti-bot risk scoring that expects consistent network behavior.&lt;/li&gt;
  &lt;li&gt;Multi-step flows like pagination, filters, redirects, and login challenges.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Recommended session patterns (copy/paste)&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;Sticky by workflow:&lt;/strong&gt; one session per job run. Great for long flows.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Sticky by entity:&lt;/strong&gt; one session per account, category, or keyword bucket.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Sticky with TTL:&lt;/strong&gt; 10 to 30 minutes, then rotate so you do not burn an IP into a known-bad state.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;Failure mode checklist&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;If you see 302 loops, assume the session is flagged. Rotate session and clear cookies.&lt;/li&gt;
  &lt;li&gt;If CAPTCHAs increase with stickiness, your sessions are too "hot." Reduce TTL and concurrency per session.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some proxy providers expose first-class sticky session controls, others make it awkward. If you already use a proxy management layer like &lt;a href="https://www.zyte.com/smart-proxy-manager/" rel="noopener noreferrer"&gt;Zyte's proxy manager&lt;/a&gt;, check how they handle session affinity and error transparency. Those details matter more than headline features from any proxy provider.&lt;/p&gt;

&lt;h2&gt;Web scraping proxies + fingerprinting: anti-bot evasion is incomplete without browser alignment&lt;/h2&gt;

&lt;p&gt;This is where teams lose time. They keep swapping proxy providers, but the actual problem is the browser layer. Or they fix the browser layer, but keep sending traffic through an IP range that the site already distrusts. Either way, you end up chasing ghosts and burning through proxy solutions that never had a chance.&lt;/p&gt;

&lt;h3&gt;Common mismatch patterns that trigger blocks&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Datacenter IP with a "perfect" human Chrome profile and long-lived cookies. It looks curated.&lt;/li&gt;
  &lt;li&gt;Residential IP with headless signals, odd WebGL/canvas entropy, and inconsistent timezone.&lt;/li&gt;
  &lt;li&gt;Mobile user-agent with a desktop viewport running on a datacenter ASN.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Practical alignment rules&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;If you use Playwright, Puppeteer, or Selenium, align timezone, language, fonts, WebRTC behavior, and viewport.&lt;/li&gt;
  &lt;li&gt;If you route through mobile proxies, use realistic device metrics and network behavior.&lt;/li&gt;
  &lt;li&gt;Keep a fingerprint profile per route. Not per codebase. Your target cares about patterns, not your repo structure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are generating scrapers quickly for new targets, an AI starter can save hours, but only if it respects production reality. Tools like the &lt;a href="https://scrapeops.io/ai-web-scraping-assistant/scraper-builder/?fpr=noorsimar" rel="noopener noreferrer"&gt;AI Scraper Builder&lt;/a&gt; can generate code across stacks and detect when JavaScript rendering is needed. Treat that output as a baseline, then wire it into your routing and monitoring so it survives real traffic and real web scraping projects.&lt;/p&gt;

&lt;h2&gt;Proxy server monitoring: proxy pool health (treat proxies like any other dependency)&lt;/h2&gt;

&lt;p&gt;A proxy server is not a magic tunnel. It is a dependency with failure modes. If you do not measure it, you will keep blaming your scraper. This is especially true once you’re running a proxy pool and switching between residential proxies, datacenter proxies, isp proxies, and mobile proxies.&lt;/p&gt;

&lt;h3&gt;The 4 metrics that matter&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;Success rate&lt;/strong&gt; by provider, geo, and page type.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Block rate&lt;/strong&gt; split by 403, 429, and CAPTCHA markers.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Latency percentiles&lt;/strong&gt; (p50 and p95). Mean latency hides pain.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Cost per successful page&lt;/strong&gt; including retries and rendering surcharges.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Alerts that reduce on-call pain (without noise)&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Success rate drop above X% for 10 minutes on a critical target.&lt;/li&gt;
  &lt;li&gt;p95 latency breach plus backlog growth in your job queue.&lt;/li&gt;
  &lt;li&gt;Cost per success doubles week-over-week. That usually means routing drift or retries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you do not want to wire all of this into Grafana yourself, a managed view helps. ScrapeOps has a &lt;a href="https://scrapeops.io/monitoring-scheduling/?fpr=noorsimar" rel="noopener noreferrer"&gt;scraper health dashboard&lt;/a&gt; approach that pairs monitoring with scheduling, which is the combo most scraping teams end up needing anyway once jobs multiply.&lt;/p&gt;

&lt;h2&gt;Best proxies for web scraping: why the "best proxy" question is a trap (benchmark your target URL)&lt;/h2&gt;

&lt;p&gt;Generic proxy rankings are a comfort blanket. They get clicks, but they rarely predict your outcome. The "best proxies for web scraping" for a product page can be different from the best web scraping proxies for search pages on the same site. Different defenses. Different payload. Different risk scoring. That’s why the best web scraping is usually boring: define routes, measure, and iterate.&lt;/p&gt;

&lt;h3&gt;Why generic proxy rankings lie (even when well-intentioned)&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Providers optimize for different verticals and traffic patterns.&lt;/li&gt;
  &lt;li&gt;One provider may crush PDP pages but fail on search and category pages.&lt;/li&gt;
  &lt;li&gt;Costs vary wildly once you add JS rendering, bandwidth, and retries.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;A benchmark workflow you can run this week&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Pick 3 representative URLs per target: search results, product/detail page, category/browse page.&lt;/li&gt;
  &lt;li&gt;Run 50 to 200 GET requests per provider configuration with consistent headers and pacing.&lt;/li&gt;
  &lt;li&gt;Record success rate, latency, CAPTCHA rate, and estimated cost per 1,000 successful pages.&lt;/li&gt;
  &lt;li&gt;Choose a primary and a fallback provider per page type, not per domain.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;Shortcut: use a proxy benchmarking tool instead of building a harness&lt;/h4&gt;

&lt;p&gt;If you want a fast answer to "which provider works best for my target website", the &lt;a href="https://scrapeops.io/proxy-providers/tester/?fpr=noorsimar" rel="noopener noreferrer"&gt;proxy tester&lt;/a&gt; from ScrapeOps runs a live benchmark against your submitted URL and emails a report. It is not a static list. It is closer to how you actually buy proxies in the real world: test, compare, decide (and avoid buying proxies blind).&lt;/p&gt;

&lt;p&gt;There is also a broader &lt;a href="https://scrapeops.io/proxy-providers/comparison/?fpr=noorsimar" rel="noopener noreferrer"&gt;proxy comparison&lt;/a&gt; page that is useful when you are mapping features across proxy types for web scraping. Use it as a starting inventory, then trust benchmarks over opinions.&lt;/p&gt;

&lt;h2&gt;Proxy provider for web scraping: routing architecture patterns (from "script" to "scraping platform")&lt;/h2&gt;

&lt;h3&gt;Pattern A: Single scraper to multi-provider routing gateway&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Scraper sends requests to one internal endpoint.&lt;/li&gt;
  &lt;li&gt;Gateway decides provider, geo, session, retries, and backoff.&lt;/li&gt;
  &lt;li&gt;Pros: centralized control and consistent policies.&lt;/li&gt;
  &lt;li&gt;Cons: you must make it observable, or you just moved the problem.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Pattern B: Domain-based micro-policies (pragmatic, scalable)&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Policy file per target domain and page type.&lt;/li&gt;
  &lt;li&gt;CI validates policy schema before deploy.&lt;/li&gt;
  &lt;li&gt;Rollback is a config revert, not a code redeploy.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Pattern C: Proxy aggregator plus scheduler plus health monitoring&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Jobs grow. Targets grow. Ownership blurs. This is where systems get messy.&lt;/li&gt;
  &lt;li&gt;Either you build the platform yourself, or you adopt infrastructure that already exists.&lt;/li&gt;
  &lt;li&gt;ScrapeOps is one option here, especially if you want proxy management, scheduling, and monitoring in one place. Many teams stitch together Airflow or Temporal, vendor proxy APIs, and internal routing code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you like the aggregator approach but want to keep provider choice flexible, start with a single endpoint and grow from there. For example, ScrapeOps offers &lt;a href="https://scrapeops.io/proxy-aggregator/?fpr=noorsimar" rel="noopener noreferrer"&gt;our proxy aggregator&lt;/a&gt; as a rotating proxy solution that can route across multiple proxy providers through one API. It is not the only way to do it, but it matches how production teams prefer to operate: fewer integration points, more control through policy.&lt;/p&gt;

&lt;h2&gt;Web scraper workflows: n8n-ready routing (scraping with proxies without building a control plane)&lt;/h2&gt;

&lt;p&gt;Lots of automation teams do not want to ship a whole new service just to manage scraping with proxies. Fair. If you are already living in n8n, you can still get 80% of the value by treating routing as a workflow step and by logging the egress IP and IP address for every critical web request.&lt;/p&gt;

&lt;h3&gt;A simple n8n flow for dynamic routing&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Trigger: Cron or webhook.&lt;/li&gt;
  &lt;li&gt;Fetch URL node: call your routing endpoint or proxy API.&lt;/li&gt;
  &lt;li&gt;IF node: if 403/429/CAPTCHA markers show up, branch to an escalation route.&lt;/li&gt;
  &lt;li&gt;Parse node: extract and validate fields, then store the output.&lt;/li&gt;
  &lt;li&gt;Metrics: write route stats to Postgres, BigQuery, or ClickHouse.&lt;/li&gt;
  &lt;li&gt;Alert: Slack or email when thresholds breach.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;What to persist per run (for debugging)&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;Chosen route and proxy provider metadata (including proxy type, geo, and session ID).&lt;/li&gt;
  &lt;li&gt;Retry chain history, not just the final outcome.&lt;/li&gt;
  &lt;li&gt;Output hash or record counts to catch silent partial failures.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your pipeline includes structured extraction, consider separating "fetch" from "parse." For certain common targets, using a dedicated parser API can simplify downstream logic. ScrapeOps has a &lt;a href="https://scrapeops.io/parser-api/?fpr=noorsimar" rel="noopener noreferrer"&gt;Parser API&lt;/a&gt; that turns raw HTML into structured JSON for supported sites, which can reduce the amount of brittle selector work in your automation layer.&lt;/p&gt;

&lt;h2&gt;Operational runbook: when the target changes their defenses overnight&lt;/h2&gt;

&lt;h3&gt;Incident checklist (copy/paste)&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Confirm it’s not your deploy. Roll back if needed.&lt;/li&gt;
  &lt;li&gt;Compare failures by page type. Search vs PDP vs category often diverge.&lt;/li&gt;
  &lt;li&gt;Check correlation: is it one provider, one geo, one proxy type, one IP range?&lt;/li&gt;
  &lt;li&gt;Switch to your fallback route and lower concurrency.&lt;/li&gt;
  &lt;li&gt;Add jitter and backoff. Stop hammering during a block event.&lt;/li&gt;
  &lt;li&gt;If you run a browser, rotate fingerprint profile and confirm alignment with network.&lt;/li&gt;
  &lt;li&gt;Post-incident: update the domain policy and add a regression test URL.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Regression tests that prevent repeat incidents&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Daily canary per target: 10 to 20 requests, same URLs, same parse checks.&lt;/li&gt;
  &lt;li&gt;Alert on success rate drop and response-body anomalies, not just status codes.&lt;/li&gt;
  &lt;li&gt;Store a small HTML sample for diffing, but avoid sensitive data and credentials.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One quiet win here is having a fast way to generate a new baseline scraper when layouts change. If you want a starting point in Python or Node across frameworks, the ScrapeOps &lt;a href="https://scrapeops.io/app/ai-assistant/scraper-generator?fpr=noorsimar" rel="noopener noreferrer"&gt;AI scraper generator&lt;/a&gt; can create editable code from a handful of sample URLs. It won’t replace engineering judgment, but it can shorten the "site changed, now what" loop for web scraping projects.&lt;/p&gt;

&lt;h2&gt;Cost control: proxy routing is mostly about retries you shouldn’t be doing&lt;/h2&gt;

&lt;h3&gt;The math most teams ignore&lt;/h3&gt;

&lt;p&gt;If success rate drops from 95% to 80%, your retry volume does not just increase a bit. It can explode, depending on your retry policy. That means more proxy traffic, more bandwidth, higher latency, and a pipeline that misses deadlines.&lt;/p&gt;

&lt;p&gt;Cost is not just "price per GB" or "price per request." It is cost per successful page, plus the engineering time spent babysitting a flaky route. The fastest way to reduce costs is usually to reduce wasteful web requests, not to chase a cheaper proxy plan.&lt;/p&gt;

&lt;h3&gt;Cost levers that don’t reduce reliability&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Split crawl and extract tiers. Crawl can be cheap and fast. Extract can be slower, stickier, and safer.&lt;/li&gt;
  &lt;li&gt;Cache stable pages where possible (ETag, Last-Modified, or your own snapshot cache).&lt;/li&gt;
  &lt;li&gt;Stop retrying non-retryable failures. A consistent 404 is not a transient error.&lt;/li&gt;
  &lt;li&gt;Quarantine bad routes quickly. Do not pay to learn the same lesson 500 times.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are evaluating proxy providers mostly on price, take a step back and benchmark value. Some teams also like providers like &lt;a href="https://decodo.com/proxies/residential-proxies" rel="noopener noreferrer"&gt;Decodo&lt;/a&gt; for their residential proxies when they need coverage at a certain price point, but again, the right move is to test against your target, not your spreadsheet assumptions.&lt;/p&gt;

&lt;h2&gt;Scraping proxy providers and tools (non-exhaustive, pick what fits your ops model)&lt;/h2&gt;

&lt;h3&gt;Proxy and scraping infrastructure options teams evaluate&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;Proxy networks and aggregators:&lt;/strong&gt; &lt;a href="https://brightdata.com/proxy-types/residential-proxies" rel="noopener noreferrer"&gt;Bright Data&lt;/a&gt;, &lt;a href="https://decodo.com/proxies/residential-proxies" rel="noopener noreferrer"&gt;Decodo&lt;/a&gt;, NodeMaven, ScrapeOps Proxy Aggregator.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Scraping APIs:&lt;/strong&gt; Zyte API, ScraperAPI, ScrapingBee, Scrapfly (these often function as a scraping api + proxy network bundle).&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Browser tooling:&lt;/strong&gt; Playwright, Puppeteer, Selenium.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Extraction tooling:&lt;/strong&gt; FireCrawl is popular for certain content extraction workflows, especially when you do not want to manage a full browser fleet.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;AI-assisted scaffolding:&lt;/strong&gt; tools that generate starter scrapers, like ScrapeOps AI Scraper Builder, can speed up new target onboarding.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Proxy protocols:&lt;/strong&gt; choose the right mix of http proxies and https proxies depending on your targets and tooling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pick based on observability, failure handling, and integration effort. A flashy bypass feature is nice. A clear error taxonomy and predictable retries are nicer. For many scraping needs, proxies come in a few consistent forms (residential, datacenter, ISP, mobile), and proxies are best when you treat them as infrastructure.. not magic.&lt;/p&gt;

&lt;h2&gt;Copyable templates (designed to earn backlinks)&lt;/h2&gt;

&lt;h3&gt;Template 1: Proxy routing policy skeleton&lt;/h3&gt;

&lt;p&gt;If you want one artifact to share internally, make it this. A small schema and strong defaults will push your team out of "random proxy rotation" and into actual proxy routing for web scraping.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;match: host, page_type, and risk tier&lt;/li&gt;
  &lt;li&gt;primary route: provider, proxy type, geo, session mode&lt;/li&gt;
  &lt;li&gt;fallback routes: ordered ladder with stop conditions&lt;/li&gt;
  &lt;li&gt;thresholds: 403/429/CAPTCHA and latency triggers&lt;/li&gt;
  &lt;li&gt;quarantine: failure count, quarantine TTL, probe rules&lt;/li&gt;
  &lt;li&gt;fingerprint: profile id or settings pointer for browser routes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Template 2: Proxy provider evaluation checklist (choosing the right proxy)&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Geo and ASN diversity: can you target specific regions and avoid poisoned subnets?&lt;/li&gt;
  &lt;li&gt;Block handling: what happens on 403/429? Do they expose real response bodies?&lt;/li&gt;
  &lt;li&gt;Session support: sticky session behavior, TTL, and whether it is consistent.&lt;/li&gt;
  &lt;li&gt;Observability: request IDs, per-request logs, error transparency.&lt;/li&gt;
  &lt;li&gt;Cost model clarity: rendering fees, bandwidth, retries, overage rules.&lt;/li&gt;
  &lt;li&gt;Dedicated proxies vs shared pools: when you need predictable session behavior, dedicated proxies or static residential proxies can be worth testing.&lt;/li&gt;
  &lt;li&gt;Residential proxy service sourcing: ask how the residential proxies use is obtained and what controls exist for abuse and churn.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Template 3: Benchmark spreadsheet columns&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Provider and configuration&lt;/li&gt;
  &lt;li&gt;Target URL and page type&lt;/li&gt;
  &lt;li&gt;Success rate and CAPTCHA rate&lt;/li&gt;
  &lt;li&gt;Latency p50 and p95&lt;/li&gt;
  &lt;li&gt;Estimated cost per 1,000 successes&lt;/li&gt;
  &lt;li&gt;Notes: anomalies, ban patterns, failure signatures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want a structured way to compare providers without doing it manually, the ScrapeOps &lt;a href="https://scrapeops.io/proxy-providers/comparison/?fpr=noorsimar" rel="noopener noreferrer"&gt;proxy comparison&lt;/a&gt; tool is handy as a catalog, and the proxy tester is the closer when you need results against a real target. When you’re comparing web scraping proxy providers, always evaluate a web scraping proxy type per page class, not just per domain.&lt;/p&gt;

&lt;h2&gt;FAQ&lt;/h2&gt;

&lt;h3&gt;What’s the best proxy for web scraping in 2026?&lt;/h3&gt;

&lt;p&gt;The best proxy for web is the one that wins on your target URLs, with your traffic pattern, at your required geo.. and keeps that win rate stable over time. Static rankings age badly. Your target’s defenses change faster than blog posts do. That’s why "web scraping in 2026" is less about a single best proxy and more about routing, monitoring, and fast recovery.&lt;/p&gt;

&lt;h3&gt;Should I always use residential proxies?&lt;/h3&gt;

&lt;p&gt;No. Residential proxies are a tool, not a religion. Start with datacenter proxies when you can, then escalate by page type and risk. If your pipeline is well-routed, you use expensive routes only when they buy you success.&lt;/p&gt;

&lt;h3&gt;How many proxy providers should I use?&lt;/h3&gt;

&lt;p&gt;For most teams, two is enough for critical targets: a primary and a fallback. More providers can help, but only if you have routing and monitoring that can handle partial failures cleanly. This is where many scraping proxy providers look similar on paper, but differ in error transparency and stability.&lt;/p&gt;

&lt;h3&gt;How do I know if blocks are fingerprint vs IP?&lt;/h3&gt;

&lt;p&gt;If rotating proxies does not improve success rate, suspect fingerprint and behavior. If rotating fingerprints does not help, suspect IP reputation, ASN, or geo issues. Most real incidents are a mix, which is why proxy routing and browser fingerprint alignment belong in the same conversation.&lt;/p&gt;

&lt;h2&gt;Closing: if it can’t be observed and recovered, it’s not production-ready (web scraping in 2026)&lt;/h2&gt;

&lt;p&gt;Vibe scraping is fine for a prototype. Production web scraping is different. You need policies, dynamic routing, sticky sessions where they matter, geo that is validated, and proxy pool health monitoring that catches drift before your stakeholders do. Build a proxy network strategy that fits your scraping needs, not a "one proxy to rule them all" fantasy.&lt;/p&gt;

&lt;p&gt;If you take one thing from this: stop asking "what are the best proxies for web scraping?" Start asking "what proxy routing for web scraping policy keeps success stable under partial failures, and what’s my rollback when the target changes?" That question leads to calm systems.. and it’s how you actually choose the best web scraping proxies for your own routes.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>vibescraping</category>
      <category>vibecoding</category>
      <category>programming</category>
    </item>
    <item>
      <title>Building a Production-Ready Apple Store Reviews Scraper with Python &amp; Scrapy</title>
      <dc:creator>Noorsimar Singh</dc:creator>
      <pubDate>Thu, 17 Jul 2025 17:19:29 +0000</pubDate>
      <link>https://dev.to/noorsimar/building-a-production-ready-apple-store-reviews-scraper-with-python-scrapy-4g72</link>
      <guid>https://dev.to/noorsimar/building-a-production-ready-apple-store-reviews-scraper-with-python-scrapy-4g72</guid>
      <description>&lt;p&gt;&lt;em&gt;Extract app reviews from 15+ countries with robust error handling, anti-detection, and multi-format exports.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Built a scalable Apple Store reviews scraper using Python and Scrapy that automatically detects countries, handles rate limiting, and exports clean data. Features include proxy rotation, deduplication, and comprehensive error handling. Perfect for app market research and sentiment analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem That Started It All
&lt;/h2&gt;

&lt;p&gt;Ever tried to analyze app reviews across different countries? I found myself manually copying data from Apple Store pages, which was... let's just say not my finest hour. The reviews were there, but getting them systematically was a nightmare.&lt;/p&gt;

&lt;p&gt;Apple Store's dynamic content, rate limiting, and country-specific layouts made traditional scraping approaches fail spectacularly. I needed something that could handle the complexity while staying respectful of Apple's servers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;App reviews are goldmines of user feedback, but they're scattered across different countries with varying languages and formats. For app developers, marketers, or researchers, having access to this data can mean the difference between a successful launch and a flop.&lt;/p&gt;

&lt;p&gt;The challenge? Apple Store uses JavaScript-heavy pages, implements aggressive rate limiting, and serves different content based on your location. A simple &lt;code&gt;requests&lt;/code&gt; + &lt;code&gt;BeautifulSoup&lt;/code&gt; approach just won't cut it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: A Robust Scrapy Spider
&lt;/h2&gt;

&lt;p&gt;I built a production-ready scraper that handles all these challenges. Here's how it works:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Smart Country Detection
&lt;/h3&gt;

&lt;p&gt;The scraper automatically detects the country from the Apple Store URL and configures the proxy accordingly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_app_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Parse Apple Store URL to extract country code and app ID&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;parsed_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;urlparse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;path_parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parsed_url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Extract country code (e.g., 'us', 'in', 'gb')
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path_parts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;country_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path_parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;# Extract app ID from the path
&lt;/span&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;path_parts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:].&lt;/span&gt;&lt;span class="nf"&gt;isdigit&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;  &lt;span class="c1"&gt;# Remove 'id' prefix
&lt;/span&gt;                &lt;span class="k"&gt;break&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means you can scrape the same app from different countries without changing any code. The scraper supports 15+ countries including US, India, UK, Canada, Australia, and more.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Robust Data Extraction with Fallbacks
&lt;/h3&gt;

&lt;p&gt;Apple Store's HTML structure can be... unpredictable. I implemented multiple fallback strategies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_app_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Extract app information with robust fallbacks&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# 1. Try meta og:title
&lt;/span&gt;    &lt;span class="n"&gt;app_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;meta[property=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;og:title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;]::attr(content)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# 2. Try JSON-LD block
&lt;/span&gt;    &lt;span class="n"&gt;json_ld&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;script[type=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/ld+json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;]::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;app_name&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;json_ld&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json_ld&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;app_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;pass&lt;/span&gt;

    &lt;span class="c1"&gt;# 3. Try &amp;lt;title&amp;gt; tag
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;app_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;app_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;title::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;app_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;app_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app_name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; - Ratings and Reviews&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach ensures we get the app name even if Apple changes their HTML structure.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Anti-Detection Features
&lt;/h3&gt;

&lt;p&gt;The scraper includes several features to avoid being blocked:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User-agent rotation&lt;/strong&gt;: Different browsers and devices&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Random delays&lt;/strong&gt;: 2-3 seconds between requests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proxy rotation&lt;/strong&gt;: Using ScrapeOps for reliable IP rotation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JavaScript rendering&lt;/strong&gt;: Handles dynamic content loading&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Production-Ready Data Pipeline
&lt;/h3&gt;

&lt;p&gt;I implemented a comprehensive pipeline system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ITEM_PIPELINES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;appstore_reviews_scraper.pipelines.AppStoreReviewsDeduplicationPipeline&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;appstore_reviews_scraper.pipelines.AppStoreReviewsLoggingPipeline&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;appstore_reviews_scraper.pipelines.AppStoreReviewsJsonPipeline&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;appstore_reviews_scraper.pipelines.AppStoreReviewsCsvPipeline&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This handles deduplication, logging, and exports to both JSON and CSV formats.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;The setup is straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clone the repository&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   git clone https://github.com/Simple-Python-Scrapy-Scrapers/appstore-reviews-scrapy-scraper.git
   &lt;span class="nb"&gt;cd &lt;/span&gt;appstore-reviews-scrapy-scraper
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create a virtual environment&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   python &lt;span class="nt"&gt;-m&lt;/span&gt; venv venv
   &lt;span class="nb"&gt;source &lt;/span&gt;venv/bin/activate  &lt;span class="c"&gt;# On Windows: venv\Scripts\activate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install dependencies&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Grab a free ScrapeOps &lt;a href="https://scrapeops.io/app/register/main/" rel="noopener noreferrer"&gt;API key&lt;/a&gt;&lt;/strong&gt; (this saved me hours of proxy setup)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Configure your API key&lt;/strong&gt; in &lt;code&gt;appstore_reviews_scraper/settings.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;   &lt;span class="n"&gt;SCRAPEOPS_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Running the Scraper
&lt;/h2&gt;

&lt;p&gt;Now for the fun part - actually running the scraper. I use Scrapy commands directly for better control and debugging:&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic Usage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Scrape 50 reviews from Instagram US&lt;/span&gt;
scrapy crawl appstore_reviews &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;app_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://apps.apple.com/us/app/instagram/id389801252"&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;max_reviews&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;50

&lt;span class="c"&gt;# Scrape 100 reviews from TikTok India&lt;/span&gt;
scrapy crawl appstore_reviews &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;app_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://apps.apple.com/in/app/tiktok/id835599320"&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;max_reviews&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100

&lt;span class="c"&gt;# Scrape 200 reviews from WhatsApp UK&lt;/span&gt;
scrapy crawl appstore_reviews &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;app_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://apps.apple.com/gb/app/whatsapp-messenger/id310633997"&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;max_reviews&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Advanced Usage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Scrape with custom settings&lt;/span&gt;
scrapy crawl appstore_reviews &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;app_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://apps.apple.com/us/app/spotify-music/id324684580"&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;max_reviews&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;75 &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;DOWNLOAD_DELAY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3

&lt;span class="c"&gt;# Enable debug logging&lt;/span&gt;
scrapy crawl appstore_reviews &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;app_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://apps.apple.com/ca/app/netflix/id363590051"&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;max_reviews&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;150 &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;LOG_LEVEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;DEBUG

&lt;span class="c"&gt;# Run with specific output format&lt;/span&gt;
scrapy crawl appstore_reviews &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;app_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://apps.apple.com/us/app/instagram/id389801252"&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;max_reviews&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;25 &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;FEEDS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{"data/reviews.json": {"format": "json"}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What Happens When You Run It
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Country Detection&lt;/strong&gt;: The scraper automatically detects the country from the URL&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proxy Configuration&lt;/strong&gt;: Sets up ScrapeOps proxy for that specific country&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Extraction&lt;/strong&gt;: Extracts app info and reviews with fallback strategies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pipeline Processing&lt;/strong&gt;: Deduplicates, validates, and exports data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File Generation&lt;/strong&gt;: Creates timestamped JSON and CSV files in the &lt;code&gt;data/&lt;/code&gt; directory&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What You Get
&lt;/h2&gt;

&lt;p&gt;The scraper extracts comprehensive data:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;App Information:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;App ID, name, developer&lt;/li&gt;
&lt;li&gt;Category, pricing, overall rating&lt;/li&gt;
&lt;li&gt;Size, version, compatibility&lt;/li&gt;
&lt;li&gt;Age rating, languages supported&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Review Data:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reviewer name and rating (1-5 stars)&lt;/li&gt;
&lt;li&gt;Review title and full text&lt;/li&gt;
&lt;li&gt;Review date and helpful votes&lt;/li&gt;
&lt;li&gt;Country-specific metadata&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Sample Output
&lt;/h3&gt;

&lt;p&gt;The scraper generates files like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;apps_20250714_183332.json&lt;/code&gt; - App information&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reviews_20250714_183332.csv&lt;/code&gt; - Review data in CSV format&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reviews_20250714_183332.json&lt;/code&gt; - Review data in JSON format&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Advanced Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Multi-Country Support
&lt;/h3&gt;

&lt;p&gt;The scraper automatically detects and handles different Apple Store regions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# US Instagram reviews&lt;/span&gt;
scrapy crawl appstore_reviews &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;app_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://apps.apple.com/us/app/instagram/id389801252"&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;max_reviews&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100

&lt;span class="c"&gt;# Indian TikTok reviews  &lt;/span&gt;
scrapy crawl appstore_reviews &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;app_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://apps.apple.com/in/app/tiktok/id835599320"&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;max_reviews&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100

&lt;span class="c"&gt;# UK WhatsApp reviews&lt;/span&gt;
scrapy crawl appstore_reviews &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;app_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://apps.apple.com/gb/app/whatsapp-messenger/id310633997"&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;max_reviews&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Custom Settings Override
&lt;/h3&gt;

&lt;p&gt;You can override any Scrapy setting on the command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Faster scraping (use with caution)&lt;/span&gt;
scrapy crawl appstore_reviews &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;app_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;max_reviews&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;50 &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;DOWNLOAD_DELAY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;CONCURRENT_REQUESTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3

&lt;span class="c"&gt;# More conservative scraping&lt;/span&gt;
scrapy crawl appstore_reviews &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;app_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;max_reviews&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;50 &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;DOWNLOAD_DELAY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5 &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;CONCURRENT_REQUESTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Debug Mode
&lt;/h3&gt;

&lt;p&gt;When things go wrong (and they will), enable debug mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;scrapy crawl appstore_reviews &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;app_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;max_reviews&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5 &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;LOG_LEVEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;DEBUG
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will show you exactly what's happening, including the HTML responses saved to the &lt;code&gt;data/&lt;/code&gt; directory for analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Technical Deep Dive
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How the Spider Works
&lt;/h3&gt;

&lt;p&gt;The spider follows this flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start Request&lt;/strong&gt;: Parses the app URL to extract country and app ID&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proxy Setup&lt;/strong&gt;: Configures ScrapeOps proxy for the detected country&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Page Fetching&lt;/strong&gt;: Downloads the reviews page with JavaScript rendering&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Extraction&lt;/strong&gt;: Uses robust selectors with multiple fallbacks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pipeline Processing&lt;/strong&gt;: Deduplicates and exports data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pagination&lt;/strong&gt;: Follows next page links if more reviews are needed&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Key Technical Decisions
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Why Scrapy over requests?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Built-in retry mechanisms&lt;/li&gt;
&lt;li&gt;Automatic request queuing&lt;/li&gt;
&lt;li&gt;Pipeline system for data processing&lt;/li&gt;
&lt;li&gt;Better handling of concurrent requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why multiple fallback strategies?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Apple Store's HTML structure changes frequently&lt;/li&gt;
&lt;li&gt;Different countries may have slightly different layouts&lt;/li&gt;
&lt;li&gt;Ensures robustness against future changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why ScrapeOps proxy?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Handles JavaScript rendering automatically&lt;/li&gt;
&lt;li&gt;Country-specific proxy rotation&lt;/li&gt;
&lt;li&gt;Built-in rate limiting and retry logic&lt;/li&gt;
&lt;li&gt;Reliable and scalable&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Learning Resources
&lt;/h2&gt;

&lt;p&gt;While building this scraper, I found these resources incredibly helpful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://scrapeops.io/websites/apple" rel="noopener noreferrer"&gt;Apple Website Analyzer&lt;/a&gt;&lt;/strong&gt;: Comprehensive analysis of Apple Store's scraping challenges, legal considerations, and technical details&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://scrapeops.io/websites/apple/how-to-scrape-apple" rel="noopener noreferrer"&gt;How to Scrape Apple Store Guide&lt;/a&gt;&lt;/strong&gt;: Step-by-step tutorials and best practices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These helped me understand the nuances of Apple Store scraping and implement the right strategies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why ScrapeOps Made the Difference
&lt;/h2&gt;

&lt;p&gt;I initially tried building my own proxy rotation system. It was... not fun. After grabbing a free ScrapeOps API key, the whole process became much smoother. Their proxy service handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Country-specific proxies&lt;/strong&gt;: Automatically matches the target country&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JavaScript rendering&lt;/strong&gt;: Handles dynamic content without additional setup&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limiting&lt;/strong&gt;: Built-in delays and retry mechanisms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analytics&lt;/strong&gt;: Monitor your scraping performance&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Troubleshooting Common Issues
&lt;/h2&gt;

&lt;h3&gt;
  
  
  "No review containers found"
&lt;/h3&gt;

&lt;p&gt;This usually means Apple changed their HTML structure or the proxy didn't return the expected content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: Check the saved HTML files in the &lt;code&gt;data/&lt;/code&gt; directory and update selectors if needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  "Proxy connection failed"
&lt;/h3&gt;

&lt;p&gt;Your ScrapeOps API key might be invalid or you've exceeded your quota.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: Verify your API key and check your ScrapeOps dashboard for usage.&lt;/p&gt;

&lt;h3&gt;
  
  
  "Rate limited"
&lt;/h3&gt;

&lt;p&gt;You're making requests too quickly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: Increase &lt;code&gt;DOWNLOAD_DELAY&lt;/code&gt; in your command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;scrapy crawl appstore_reviews &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;app_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;max_reviews&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;50 &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nv"&gt;DOWNLOAD_DELAY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;The scraper is production-ready, but here are some enhancements you could add:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Sentiment analysis&lt;/strong&gt;: Process review text for sentiment scores&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database integration&lt;/strong&gt;: Store data in PostgreSQL or MongoDB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduling&lt;/strong&gt;: Set up automated scraping with cron jobs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API endpoint&lt;/strong&gt;: Create a REST API for the scraper&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dashboard&lt;/strong&gt;: Build a web interface for monitoring and analysis&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Building this scraper taught me that modern web scraping is less about brute force and more about understanding the target website's architecture. Apple Store's dynamic content and anti-bot measures require a thoughtful approach.&lt;/p&gt;

&lt;p&gt;The key was combining robust error handling with intelligent fallbacks, while staying respectful of Apple's servers. The result is a scraper that's both reliable and maintainable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check out the full code on GitHub&lt;/strong&gt; and let me know if you build something interesting with it. If you found this helpful, consider starring the repository - it helps other developers discover useful tools like this.&lt;/p&gt;




&lt;h1&gt;
  
  
  Title Variations
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Variation 1: Problem-Solution Focus
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"How I Built a Scalable Apple Store Reviews Scraper That Actually Works"&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Variation 2: Technical Deep-Dive
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"Building a Production-Ready Web Scraper: Lessons from Apple Store"&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Variation 3: Educational Approach
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"From Manual Copy-Paste to Automated Scraping: A Python Journey"&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Variation 4: Results-Oriented
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"Extract Apple Store Reviews from 15+ Countries with This Python Scraper"&lt;/strong&gt;&lt;/p&gt;




</description>
      <category>webscraping</category>
      <category>python</category>
      <category>opensource</category>
      <category>api</category>
    </item>
    <item>
      <title>G2.com Scraping: Handling Anti-Bot Measures with Scrapy</title>
      <dc:creator>Noorsimar Singh</dc:creator>
      <pubDate>Mon, 14 Jul 2025 17:00:07 +0000</pubDate>
      <link>https://dev.to/noorsimar/g2com-scraping-handling-anti-bot-measures-with-scrapy-18eh</link>
      <guid>https://dev.to/noorsimar/g2com-scraping-handling-anti-bot-measures-with-scrapy-18eh</guid>
      <description>&lt;h1&gt;
  
  
  Building a Production-Ready G2.com Scraper with Python and Scrapy
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Learn how to build a robust web scraper for G2.com that handles anti-bot measures, exports clean data, and scales from development to production.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Built a G2.com scraper using Scrapy that extracts category listings and product reviews. Features include anti-bot detection, proxy rotation via ScrapeOps, duplicate handling, and clean CSV/JSON exports. Perfect for market research and competitive analysis.&lt;/p&gt;




&lt;p&gt;I recently needed to gather competitive intelligence from G2.com for my project. What started as a simple script quickly evolved into a production-ready scraper that handles G2's anti-bot measures, exports clean data, and scales from development to production. Here's how I built it and what I learned along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;G2.com is a goldmine for B2B market research, but it's also protected by sophisticated anti-bot measures. Most scraping attempts fail due to rate limiting, IP blocking, or JavaScript challenges. A robust scraper needs to handle these obstacles while maintaining data quality and respecting the site's policies.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;p&gt;The scraper uses two main spiders:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Category Spider&lt;/strong&gt;: Extracts product listings from category pages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Product Reviews Spider&lt;/strong&gt;: Collects detailed reviews from individual product pages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both spiders share a common pipeline for data validation, duplicate removal, and export formatting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Universal Selector Discovery
&lt;/h3&gt;

&lt;p&gt;G2's layout varies across pages, so I implemented a fallback system that tries multiple selectors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;discover_review_containers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;container_selectors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;article.elv-bg-neutral-0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;article[data-testid*=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;div[itemprop=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;# ... more fallbacks
&lt;/span&gt;    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;selector&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;container_selectors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;containers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;containers&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_verify_review_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;containers&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach ensures the scraper adapts to different page layouts without manual intervention.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Anti-Bot Middleware Stack
&lt;/h3&gt;

&lt;p&gt;The middleware pipeline handles various anti-bot measures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;DOWNLOADER_MIDDLEWARES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;g2_scraper.middlewares.RandomUserAgentMiddleware&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;g2_scraper.middlewares.RandomDelayMiddleware&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;g2_scraper.middlewares.AntiBotDetectionMiddleware&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;402&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;scrapeops_scrapy_proxy_sdk.ScrapeOpsScrapyProxySdk&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;725&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The RandomUserAgentMiddleware rotates through realistic browser signatures, while the RandomDelayMiddleware adds natural timing variations.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Smart Duplicate Detection
&lt;/h3&gt;

&lt;p&gt;Instead of simple field matching, the duplicate pipeline creates unique identifiers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ItemAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;reviewer_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;adapter&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;review_date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Reviews: reviewer + date + review snippet
&lt;/span&gt;        &lt;span class="n"&gt;review_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;review_text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;item_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;reviewer_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;review_date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;review_text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;product_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;adapter&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;product_url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Products: name + URL
&lt;/span&gt;        &lt;span class="n"&gt;item_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;product_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;product_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;product_url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;item_id&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ids_seen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;DropItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Duplicate item found: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;item_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ids_seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents duplicate reviews while allowing multiple products with the same name.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Dynamic Export Pipeline
&lt;/h3&gt;

&lt;p&gt;The export pipeline creates files only for item types that actually have data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Determine item type
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;reviewer_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;item_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;review&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;product_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;item_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;product&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;category_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;item_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

    &lt;span class="c1"&gt;# Create file only when first item of this type is encountered
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;item_type&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active_types&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data/g2_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;item_type&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="c1"&gt;# ... create file and write header
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active_types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Write data row
&lt;/span&gt;    &lt;span class="n"&gt;csv_writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writerow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clean_item_obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents empty files and keeps the output directory clean.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Category Scraping
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;scrapy crawl g2_category &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;system-security &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This extracts the top 5 products from the system-security category, including ratings, review counts, and vendor information.&lt;/p&gt;

&lt;h3&gt;
  
  
  Product Reviews Scraping
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;scrapy crawl g2_product_reviews &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;product_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://www.g2.com/products/rollworks-account-based-platform/reviews"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This collects detailed reviews with pros/cons, reviewer information, and ratings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Features That Made This Production-Ready
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;JavaScript Rendering&lt;/strong&gt;: Uses &lt;code&gt;render_js=true&lt;/code&gt; parameter for fully rendered pages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proxy Rotation&lt;/strong&gt;: Integrated ScrapeOps proxy for IP rotation and geolocation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Validation&lt;/strong&gt;: Automatic cleaning and validation of all scraped fields&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error Recovery&lt;/strong&gt;: Exponential backoff and retry mechanisms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate Limiting&lt;/strong&gt;: Respectful delays and auto-throttling&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;The biggest challenge was handling G2's dynamic content loading. Initially, I tried static selectors, but they failed when the site updated its layout. The universal selector discovery system solved this by trying multiple patterns and verifying content before proceeding.&lt;/p&gt;

&lt;p&gt;Another key insight was the importance of proper duplicate detection. Simple field matching caused issues when the same reviewer posted multiple reviews for the same product. The current approach uses a combination of reviewer name, date, and review snippet to create truly unique identifiers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clone the repository&lt;/strong&gt;: &lt;a href="https://github.com/Simple-Python-Scrapy-Scrapers/g2-scrapy-scraper" rel="noopener noreferrer"&gt;g2-scrapy-scraper&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Install dependencies&lt;/strong&gt;: &lt;code&gt;pip install -r requirements.txt&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://scrapeops.io/app/register/main" rel="noopener noreferrer"&gt;Grab a free ScrapeOps API key&lt;/a&gt;&lt;/strong&gt; for proxy rotation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run the spiders&lt;/strong&gt;: Use the commands above&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Next Steps and Resources
&lt;/h2&gt;

&lt;p&gt;For deeper insights into G2's scraping challenges, check out the &lt;a href="https://scrapeops.io/websites/g2" rel="noopener noreferrer"&gt;G2 Website Analyzer&lt;/a&gt; which covers anti-bot measures, legal considerations, and technical challenges.&lt;/p&gt;

&lt;p&gt;If you need step-by-step guidance, the &lt;a href="https://scrapeops.io/websites/g2/how-to-scrape-g2" rel="noopener noreferrer"&gt;How-to Scrape G2 Guide&lt;/a&gt; provides detailed walkthroughs for various scraping scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why ScrapeOps Made a Difference
&lt;/h2&gt;

&lt;p&gt;I initially tried building this with free proxies, but the success rate was abysmal. After grabbing a free ScrapeOps API key, the success rate jumped to 95%+. The proxy rotation and geolocation features eliminated most blocking issues, while the monitoring dashboard helped me optimize the scraping strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This scraper demonstrates how to build production-ready web scraping solutions that handle real-world challenges. The modular architecture makes it easy to extend for other sites, while the robust error handling ensures reliable operation.&lt;/p&gt;

&lt;p&gt;The complete code is available on &lt;strong&gt;&lt;a href="https://github.com/Simple-Python-Scrapy-Scrapers/g2-scrapy-scraper" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/strong&gt; - feel free to star it if you find it useful, and let me know if you have questions or suggestions for improvements.&lt;/p&gt;




</description>
      <category>webscraping</category>
      <category>opensource</category>
      <category>python</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Scraping eBay's 1.2 Billion Listings: A Scrapy Tale</title>
      <dc:creator>Noorsimar Singh</dc:creator>
      <pubDate>Fri, 11 Jul 2025 17:21:45 +0000</pubDate>
      <link>https://dev.to/noorsimar/scraping-ebays-12-billion-listings-a-scrapy-tale-2pec</link>
      <guid>https://dev.to/noorsimar/scraping-ebays-12-billion-listings-a-scrapy-tale-2pec</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Built a production-ready eBay scraper using Scrapy framework with ScrapeOps proxy integration. Handles search results, product details, price extraction, and exports clean CSV/JSON data while bypassing anti-bot measures.&lt;/p&gt;




&lt;p&gt;Last quarter, I needed to track laptop prices across eBay for a friend research project. What started as a "quick scraping script" turned into a full-featured eBay scraper that I'm now sharing as an open-source project.&lt;/p&gt;

&lt;p&gt;The challenge? eBay has sophisticated anti-bot protection, dynamic pricing, and multiple page layouts. The solution? A robust Scrapy spider with proper proxy rotation and intelligent data extraction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why eBay Scraping Matters for Developers
&lt;/h2&gt;

&lt;p&gt;E-commerce data extraction is everywhere in modern development. Whether you're building price comparison tools, market research platforms, or inventory management systems, you'll eventually need to scrape product data.&lt;/p&gt;

&lt;p&gt;eBay presents unique challenges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple anti-detection systems&lt;/li&gt;
&lt;li&gt;Complex auction vs. fixed-price listings
&lt;/li&gt;
&lt;li&gt;Global marketplace variations&lt;/li&gt;
&lt;li&gt;Real-time price changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Getting this right teaches you patterns that apply to almost any web scraping project.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture: Two Spiders, Clean Data
&lt;/h2&gt;

&lt;p&gt;I built two specialized spiders instead of one monolithic scraper:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Search Spider (&lt;code&gt;ebay_search.py&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Extracts product listings from search results pages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_search_item_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Extract data from a single search result item&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;EbaySearchItem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Skip placeholder items (common eBay issue)
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;product_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;product_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;isdigit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;product_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="c1"&gt;# Price extraction with fallbacks
&lt;/span&gt;    &lt;span class="n"&gt;price_element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.s-item__price .notranslate::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;price_element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;price_element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.s-item__price::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;current_price&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;price_element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;price_element&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Product Spider (&lt;code&gt;ebay_product.py&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Extracts detailed information from individual product pages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_product_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Extract detailed product data from eBay product page&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# Updated selectors for new eBay layout
&lt;/span&gt;    &lt;span class="n"&gt;price_element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.x-price-primary .ux-textspans::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;price_element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;price_element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.x-price-approx__price .ux-textspans::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Handle multiple currencies (AU $575.00, $199.99, etc.)
&lt;/span&gt;    &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;current_price&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;price_element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;price_element&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key insight? eBay frequently updates their CSS selectors, so I implemented cascading fallbacks that check multiple possible selectors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling Anti-Bot Protection with ScrapeOps
&lt;/h2&gt;

&lt;p&gt;Here's where most DIY scrapers fail. eBay's security is no joke. I integrated ScrapeOps proxy rotation early in the project, and honestly, it saved me weeks of debugging.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# settings.py
&lt;/span&gt;&lt;span class="n"&gt;SCRAPEOPS_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;SCRAPEOPS_PROXY_ENABLED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="n"&gt;SCRAPEOPS_RENDER_JS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="n"&gt;SCRAPEOPS_PROXY_SETTINGS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bypass&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;generic_level_2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;DOWNLOADER_MIDDLEWARES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;scrapeops_scrapy_proxy_sdk.scrapeops_scrapy_proxy_sdk.ScrapeOpsScrapyProxySdk&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;725&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;bypass: generic_level_2&lt;/code&gt; parameter was crucial for handling eBay's JavaScript challenges. Without it, I was getting blocked after just a few requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Clean Data Export Pipeline
&lt;/h2&gt;

&lt;p&gt;Raw scraped data is messy. I built validation and cleaning pipelines that output production-ready CSV and JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pipelines.py
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EbayDataCleaningPipeline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Normalize prices across different currencies
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;current_price&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;current_price&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clean_price&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;current_price&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

        &lt;span class="c1"&gt;# Standardize conditions
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;condition&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;condition&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normalize_condition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;condition&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result? Clean CSV files with consistent formatting, ready for analysis or database import.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the Scraper
&lt;/h2&gt;

&lt;p&gt;Basic usage is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Search for products&lt;/span&gt;
scrapy crawl ebay_search &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"laptop"&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;max_results&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;50

&lt;span class="c"&gt;# Get detailed product info  &lt;/span&gt;
scrapy crawl ebay_product &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;item_ids&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"123456789,987654321"&lt;/span&gt;

&lt;span class="c"&gt;# Export to specific format&lt;/span&gt;
scrapy crawl ebay_search &lt;span class="nt"&gt;-o&lt;/span&gt; results.json &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"vintage camera"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The scraper automatically handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rate limiting and delays&lt;/li&gt;
&lt;li&gt;User agent rotation
&lt;/li&gt;
&lt;li&gt;Failed request retries&lt;/li&gt;
&lt;li&gt;Data validation and cleaning&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next Steps and Enhancements
&lt;/h2&gt;

&lt;p&gt;Want to take this further? The &lt;a href="https://github.com/Simple-Python-Scrapy-Scrapers/ebay-scrapy-scraper" rel="noopener noreferrer"&gt;complete GitHub repository&lt;/a&gt; includes advanced features like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-currency price normalization&lt;/li&gt;
&lt;li&gt;Seller reputation analysis&lt;/li&gt;
&lt;li&gt;Historical price tracking setup&lt;/li&gt;
&lt;li&gt;Global eBay site support (UK, DE, CA, AU)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For deeper insights into eBay's anti-bot measures and scraping strategies, I recommend checking out the &lt;a href="https://scrapeops.io/websites/ebay" rel="noopener noreferrer"&gt;eBay Website Analyzer&lt;/a&gt; and their &lt;a href="https://scrapeops.io/websites/ebay/how-to-scrape-ebay" rel="noopener noreferrer"&gt;comprehensive scraping guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ScrapeOps Integration Experience
&lt;/h2&gt;

&lt;p&gt;Full transparency: I grabbed a &lt;a href="https://scrapeops.io/app/register/main" rel="noopener noreferrer"&gt;free ScrapeOps API key&lt;/a&gt; early in this project, and it eliminated the proxy headaches entirely. Their free tier covers 1,000 requests monthly, which is perfect for development and testing.&lt;/p&gt;

&lt;p&gt;The JavaScript rendering feature was particularly valuable for eBay's dynamic content. Instead of wrestling with Selenium or Playwright, I got reliable results with a simple configuration change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Building a production-ready web scraper taught me that the hard part isn't extracting data—it's handling all the edge cases, anti-bot measures, and data quality issues that come up in real-world usage.&lt;/p&gt;

&lt;p&gt;This eBay scraper handles those challenges with proven patterns you can apply to other e-commerce sites. The code is open source, well-documented, and battle-tested.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Found this useful?&lt;/strong&gt; Star the &lt;a href="https://github.com/Simple-Python-Scrapy-Scrapers/ebay-scrapy-scraper" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt; and let me know what you build with it. I'm always curious to see how other developers adapt these patterns for their own projects.&lt;/p&gt;

&lt;p&gt;What's your biggest web scraping challenge? Drop a comment below—I might cover it in a future post.&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>python</category>
      <category>opensource</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building a YouTube Scraper with Scrapy: A Developer's Journey</title>
      <dc:creator>Noorsimar Singh</dc:creator>
      <pubDate>Thu, 10 Jul 2025 12:03:22 +0000</pubDate>
      <link>https://dev.to/noorsimar/building-a-youtube-scraper-with-scrapy-a-developers-journey-2m0p</link>
      <guid>https://dev.to/noorsimar/building-a-youtube-scraper-with-scrapy-a-developers-journey-2m0p</guid>
      <description>&lt;p&gt;&lt;em&gt;Extract video data, channel analytics, and search results from YouTube using Python and Scrapy with proxy rotation and anti-bot protection.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Built a production-ready YouTube scraper that extracts video search results and channel analytics using Scrapy. Features include proxy rotation via ScrapeOps, data validation pipelines, and CSV/JSON export. Perfect for researchers, marketers, and developers who need reliable YouTube data extraction without hitting API limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem That Started It All
&lt;/h2&gt;

&lt;p&gt;Last year, I was working on a content analysis project and needed to gather YouTube data at scale. The official YouTube Data API has strict quotas, and manual data collection was taking forever. So I decided to build something better—a robust scraper that could handle YouTube's anti-bot measures while extracting clean, structured data.&lt;/p&gt;

&lt;p&gt;What I ended up with was a comprehensive YouTube scraper that not only works reliably but also teaches some valuable lessons about modern web scraping workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why YouTube Scraping Matters
&lt;/h2&gt;

&lt;p&gt;YouTube contains a goldmine of data for content creators, marketers, and researchers. But accessing this data programmatically comes with challenges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API Limitations&lt;/strong&gt;: YouTube Data API has strict quotas and rate limits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anti-Bot Measures&lt;/strong&gt;: YouTube actively detects and blocks automated requests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Quality&lt;/strong&gt;: Raw HTML extraction often yields inconsistent results&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scale Issues&lt;/strong&gt;: Manual data collection doesn't scale for research projects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The solution? A well-architected Scrapy-based scraper with proper proxy rotation and data validation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;p&gt;The scraper follows a clean, modular design with separate spiders for different use cases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Core spiders
&lt;/span&gt;&lt;span class="n"&gt;youtube_search&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;      &lt;span class="c1"&gt;# Extract video search results
&lt;/span&gt;&lt;span class="n"&gt;youtube_channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;     &lt;span class="c1"&gt;# Gather channel analytics
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each spider yields structured data through Scrapy's item pipeline system, ensuring clean, validated output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Features That Make It Work
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Smart Data Extraction
&lt;/h3&gt;

&lt;p&gt;The scraper uses multiple extraction strategies to handle YouTube's dynamic content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Primary: Extract from ytInitialData JavaScript objects
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_search_results&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Parse YouTube's internal data structure
&lt;/span&gt;    &lt;span class="n"&gt;yt_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extract_yt_initial_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Fallback: HTML parsing when JS extraction fails
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;yt_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_html_fallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Proxy Rotation with ScrapeOps
&lt;/h3&gt;

&lt;p&gt;To avoid IP blocking, the scraper integrates with ScrapeOps for proxy rotation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# settings.py
&lt;/span&gt;&lt;span class="n"&gt;SCRAPEOPS_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;your_api_key_here&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;SCRAPEOPS_PROXY_ENABLED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

&lt;span class="n"&gt;DOWNLOADER_MIDDLEWARES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;scrapeops_scrapy_proxy_sdk.scrapeops_scrapy_proxy_sdk.ScrapeOpsScrapyProxySdk&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;725&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I grabbed a &lt;a href="https://scrapeops.io/app/register/main" rel="noopener noreferrer"&gt;free ScrapeOps API key&lt;/a&gt; during development, and it made a huge difference in reliability. The free tier gives you 1,000 requests —perfect for testing and small projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Data Validation Pipeline
&lt;/h3&gt;

&lt;p&gt;Every scraped item goes through a validation pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;YoutubeDataValidationPipeline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Validate required fields
&lt;/span&gt;        &lt;span class="n"&gt;required_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;YoutubeSearchItem&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;video_url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;channel_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;YoutubeChannelItem&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;channel_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;channel_url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;# URL validation and data cleaning
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_is_valid_youtube_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;video_url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;DropItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invalid YouTube URL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Flexible Export System
&lt;/h3&gt;

&lt;p&gt;The scraper exports data in multiple formats with dynamic field handling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# CSV export with automatic field detection
&lt;/span&gt;&lt;span class="n"&gt;FEEDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data/youtube_search_results.csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;format&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data/youtube_channels.csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;format&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data/youtube_videos.csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;format&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Usage Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Search for Videos
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Basic search&lt;/span&gt;
scrapy crawl youtube_search &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"python programming"&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;max_results&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;50

&lt;span class="c"&gt;# Advanced search with filters&lt;/span&gt;
scrapy crawl youtube_search &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"machine learning"&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;max_results&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Extract Channel Data
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Scrape by channel handles&lt;/span&gt;
scrapy crawl youtube_channel &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;channel_handles&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"@freecodecamp,@programmingwithmosh"&lt;/span&gt;

&lt;span class="c"&gt;# Scrape by URLs&lt;/span&gt;
scrapy crawl youtube_channel &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;channel_urls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://www.youtube.com/@scrapeops"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What You Get
&lt;/h2&gt;

&lt;p&gt;The scraper extracts comprehensive data:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Search Results:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Video URLs, titles, descriptions, thumbnails&lt;/li&gt;
&lt;li&gt;Channel information and verification status&lt;/li&gt;
&lt;li&gt;View counts (normalized to integers)&lt;/li&gt;
&lt;li&gt;Upload dates and duration&lt;/li&gt;
&lt;li&gt;Content type detection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Channel Analytics:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Profile pictures and banner images&lt;/li&gt;
&lt;li&gt;Subscriber counts and video counts&lt;/li&gt;
&lt;li&gt;Channel descriptions and social links&lt;/li&gt;
&lt;li&gt;Performance metrics and engagement rates&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Data Analysis Ready
&lt;/h2&gt;

&lt;p&gt;The exported CSV files are ready for analysis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="c1"&gt;# Load scraped data
&lt;/span&gt;&lt;span class="n"&gt;search_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data/youtube_search_results.csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;channel_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data/youtube_channels.csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Analyze view distribution
&lt;/span&gt;&lt;span class="n"&gt;search_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;views_normalized&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;hist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bins&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Distribution of Video Views&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;Building this scraper taught me several important lessons about modern web scraping:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Proxy rotation is essential&lt;/strong&gt; - Without it, you'll get blocked quickly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data validation matters&lt;/strong&gt; - Clean data saves hours of post-processing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modular design pays off&lt;/strong&gt; - Separate spiders for different use cases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error handling is crucial&lt;/strong&gt; - YouTube's structure changes frequently&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Next Steps and Resources
&lt;/h2&gt;

&lt;p&gt;If you're interested in diving deeper into YouTube scraping, I recommend checking out the &lt;a href="https://scrapeops.io/websites/youtube" rel="noopener noreferrer"&gt;YouTube Website Analyzer&lt;/a&gt; for insights into scraping difficulty and legal considerations. There's also a comprehensive &lt;a href="https://scrapeops.io/websites/youtube/how-to-scrape-youtube" rel="noopener noreferrer"&gt;step-by-step guide&lt;/a&gt; that covers advanced techniques.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;

&lt;p&gt;The complete code is available on GitHub: &lt;a href="https://github.com/Simple-Python-Scrapy-Scrapers/youtube-scrapy-scraper" rel="noopener noreferrer"&gt;youtube-scrapy-scraper&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To get started:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clone the repository&lt;/li&gt;
&lt;li&gt;Install dependencies: &lt;code&gt;pip install -r requirements.txt&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Grab a &lt;a href="https://scrapeops.io/app/register/main" rel="noopener noreferrer"&gt;free ScrapeOps API key&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Update your API key in &lt;code&gt;settings.py&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run your first scrape&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;This YouTube scraper represents what modern web scraping should be: reliable, scalable, and maintainable. It's not just about extracting data—it's about building robust systems that can handle real-world challenges.&lt;/p&gt;

&lt;p&gt;The project is open-source and actively maintained. If you find it useful, consider starring the repository or contributing to make it even better. And if you run into any issues, the community is always there to help.&lt;/p&gt;

&lt;p&gt;Happy scraping!&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>python</category>
      <category>opensource</category>
      <category>youtube</category>
    </item>
    <item>
      <title>Pinterest Scraping in 2025: Why I Built a Production-Ready Scrapy Spider (And You Should Too)</title>
      <dc:creator>Noorsimar Singh</dc:creator>
      <pubDate>Wed, 09 Jul 2025 16:51:12 +0000</pubDate>
      <link>https://dev.to/noorsimar/pinterest-scraping-in-2025-why-i-built-a-production-ready-scrapy-spider-and-you-should-too-4j0n</link>
      <guid>https://dev.to/noorsimar/pinterest-scraping-in-2025-why-i-built-a-production-ready-scrapy-spider-and-you-should-too-4j0n</guid>
      <description>&lt;h2&gt;
  
  
  The Problem That Changed My Perspective
&lt;/h2&gt;

&lt;p&gt;Last month, I was helping a client analyze visual content trends for their e-commerce brand. They needed to understand what home decor pins were gaining traction, which boards were most influential, and how their competitors were performing on Pinterest. &lt;/p&gt;

&lt;p&gt;Traditional social media analytics tools? They barely scratched Pinterest's surface. Manual research? Absolutely not scalable.&lt;/p&gt;

&lt;p&gt;That's when I realized: &lt;strong&gt;Pinterest's visual data goldmine is largely untapped by developers.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Pinterest Scraping is Different in 2025
&lt;/h2&gt;

&lt;p&gt;Pinterest isn't just another social platform—it's a visual search engine with over 450 million monthly active users. But here's the catch: scraping Pinterest effectively requires understanding its unique challenges:&lt;/p&gt;

&lt;h3&gt;
  
  
  The Technical Reality Check
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Heavy JavaScript rendering&lt;/strong&gt; (goodbye, simple HTTP requests)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sophisticated anti-bot detection&lt;/strong&gt; (IP blocking is real)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic content loading&lt;/strong&gt; (infinite scroll complexity)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limiting that actually works&lt;/strong&gt; (they mean business)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I learned this the hard way when my first attempts resulted in blocked IPs and empty responses. The &lt;a href="https://scrapeops.io/websites/pinterest" rel="noopener noreferrer"&gt;Pinterest Website Analyzer&lt;/a&gt; confirmed what I suspected: this platform has some serious scraping challenges.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a Pinterest Scraper That Actually Works
&lt;/h2&gt;

&lt;p&gt;After weeks of trial and error, I built a production-ready Pinterest scraper using Scrapy. Here's what I discovered works:&lt;/p&gt;

&lt;h3&gt;
  
  
  The Three-Spider Architecture
&lt;/h3&gt;

&lt;p&gt;Instead of one monolithic scraper, I created three specialized spiders:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pinterest Pins Spider&lt;/strong&gt; - Individual pin data with engagement metrics&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pinterest Boards Spider&lt;/strong&gt; - Board analytics and user insights
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pinterest Search Spider&lt;/strong&gt; - Search results and trending content&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each spider handles different data structures and extraction patterns, making the entire system more robust.&lt;/p&gt;

&lt;h3&gt;
  
  
  JavaScript Rendering (The Game Changer)
&lt;/h3&gt;

&lt;p&gt;Pinterest's content is heavily JavaScript-dependent. Using ScrapeOps proxy with JavaScript rendering was crucial:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# This approach actually works
&lt;/span&gt;&lt;span class="n"&gt;proxy_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://proxy.scrapeops.io/v1/?api_key=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;url=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;search_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;render_js=true&amp;amp;wait=3000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The 3-second wait time ensures all dynamic content loads properly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real Success Metrics
&lt;/h3&gt;

&lt;p&gt;After optimization, my scraper achieves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;95%+ request success rate&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;25-50 pins per search query&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;15-20 boards per category&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean CSV exports&lt;/strong&gt; with timestamped data&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Data Goldmine You're Missing
&lt;/h2&gt;

&lt;p&gt;Here's what you can extract with a properly built Pinterest scraper:&lt;/p&gt;

&lt;h3&gt;
  
  
  Pin-Level Intelligence
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Engagement metrics (likes, comments, repins)&lt;/li&gt;
&lt;li&gt;Source attribution and link analysis&lt;/li&gt;
&lt;li&gt;Visual content metadata&lt;/li&gt;
&lt;li&gt;Shopping data and pricing trends&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Board Analytics
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Follower counts and growth patterns&lt;/li&gt;
&lt;li&gt;Collaboration networks&lt;/li&gt;
&lt;li&gt;Content category analysis&lt;/li&gt;
&lt;li&gt;Seasonal trend identification&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Search Insights
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Trending topics and hashtags&lt;/li&gt;
&lt;li&gt;Content discovery patterns&lt;/li&gt;
&lt;li&gt;Competitive intelligence&lt;/li&gt;
&lt;li&gt;Market sentiment analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Technical Implementation Insights
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Proxy Rotation is Non-Negotiable
&lt;/h3&gt;

&lt;p&gt;Pinterest's anti-bot measures are sophisticated. I found that using &lt;a href="https://scrapeops.io/app/register/main" rel="noopener noreferrer"&gt;ScrapeOps proxy rotation&lt;/a&gt; was essential for maintaining consistent access. Their free tier (1,000 requests) is perfect for development and testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  The CSV Pipeline Strategy
&lt;/h3&gt;

&lt;p&gt;Rather than complex databases, I optimized for CSV output with smart filtering:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Only create files for relevant data types
&lt;/span&gt;&lt;span class="n"&gt;non_empty_item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cleaned_item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
                  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;false&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach keeps data clean and immediately usable for analysis.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rate Limiting That Respects the Platform
&lt;/h3&gt;

&lt;p&gt;My configuration respects Pinterest's resources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;CONCURRENT_REQUESTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;DOWNLOAD_DELAY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="n"&gt;RANDOMIZE_DOWNLOAD_DELAY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ethical scraping isn't just good practice—it's sustainable scraping.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Applications I've Seen
&lt;/h2&gt;

&lt;h3&gt;
  
  
  E-commerce Trend Analysis
&lt;/h3&gt;

&lt;p&gt;A fashion brand used the scraper to identify emerging style trends 3 months before they peaked. They adjusted their inventory accordingly and saw 23% higher sales on trending items.&lt;/p&gt;

&lt;h3&gt;
  
  
  Content Marketing Intelligence
&lt;/h3&gt;

&lt;p&gt;A home decor blog analyzed competitor pin performance to optimize their content strategy. They identified underserved niches and increased their Pinterest traffic by 180%.&lt;/p&gt;

&lt;h3&gt;
  
  
  Market Research at Scale
&lt;/h3&gt;

&lt;p&gt;A startup used board analytics to understand consumer preferences in their target demographic, informing product development decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Resources That Made the Difference
&lt;/h2&gt;

&lt;p&gt;Building this scraper required understanding Pinterest's technical landscape:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://scrapeops.io/websites/pinterest" rel="noopener noreferrer"&gt;Pinterest Website Analyzer&lt;/a&gt; - Essential for understanding scraping challenges&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://scrapeops.io/websites/pinterest/how-to-scrape-pinterest" rel="noopener noreferrer"&gt;Pinterest Scraping Guide&lt;/a&gt; - Comprehensive technical walkthrough&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.scrapy.org/en/latest/" rel="noopener noreferrer"&gt;Scrapy Documentation&lt;/a&gt; - For framework fundamentals&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why I'm Sharing This (And the Code)
&lt;/h2&gt;

&lt;p&gt;The Pinterest data opportunity is massive, but most developers are intimidated by the technical barriers. I spent weeks solving these challenges, and I believe the solution should be accessible.&lt;/p&gt;

&lt;p&gt;I've open-sourced the complete Pinterest scraper on GitHub: &lt;a href="https://github.com/Simple-Python-Scrapy-Scrapers/pinterest-scrapy-scraper" rel="noopener noreferrer"&gt;pinterest-scrapy-scraper&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The repository includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Three specialized, production-ready spiders&lt;/li&gt;
&lt;li&gt;✅ ScrapeOps proxy integration&lt;/li&gt;
&lt;li&gt;✅ Complete documentation and usage examples&lt;/li&gt;
&lt;li&gt;✅ CSV export pipeline&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started (The Practical Way)
&lt;/h2&gt;

&lt;p&gt;If you want to explore Pinterest data:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clone the repository&lt;/strong&gt; and review the implementation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Get a free ScrapeOps API key&lt;/strong&gt; for proxy rotation (essential for success)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Start small&lt;/strong&gt; - test with 5-10 items to understand the data structure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scale gradually&lt;/strong&gt; - respect rate limits and monitor success rates&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;a href="https://scrapeops.io/websites/pinterest/how-to-scrape-pinterest" rel="noopener noreferrer"&gt;technical guide&lt;/a&gt; walks through the complete implementation process.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Future of Visual Data Intelligence
&lt;/h2&gt;

&lt;p&gt;Pinterest represents the future of visual search and content discovery. As AI and machine learning increasingly rely on visual data, platforms like Pinterest become crucial data sources for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Computer vision training datasets&lt;/li&gt;
&lt;li&gt;Consumer behavior analysis&lt;/li&gt;
&lt;li&gt;Trend prediction algorithms&lt;/li&gt;
&lt;li&gt;Content recommendation systems&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Building a Pinterest scraper taught me that the most valuable data often sits behind the most challenging technical barriers. Pinterest's anti-bot measures exist for good reasons, but ethical, respectful scraping can coexist with platform protection.&lt;/p&gt;

&lt;p&gt;The key is building tools that respect the platform while extracting meaningful insights. My Pinterest scraper does exactly that—and now it's available for anyone facing similar challenges.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have you tackled Pinterest scraping challenges? Share your experiences in the comments. And if you build something interesting with the scraper, I'd love to hear about it!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>pinterest</category>
      <category>webscraping</category>
      <category>python</category>
      <category>scrapy</category>
    </item>
    <item>
      <title>Building a Production-Ready Target Scraper: 5 Spiders That Actually Work in 2024</title>
      <dc:creator>Noorsimar Singh</dc:creator>
      <pubDate>Mon, 07 Jul 2025 17:03:45 +0000</pubDate>
      <link>https://dev.to/noorsimar/building-a-production-ready-target-scraper-5-spiders-that-actually-work-in-2024-48lo</link>
      <guid>https://dev.to/noorsimar/building-a-production-ready-target-scraper-5-spiders-that-actually-work-in-2024-48lo</guid>
      <description>&lt;p&gt;&lt;em&gt;How I built a comprehensive Target.com scraping solution and what I learned about e-commerce data extraction&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I Built This (And Why You Might Need It Too)
&lt;/h2&gt;

&lt;p&gt;Last month, I was helping a friend analyze pricing trends for their dropshipping business. They were manually checking Target prices for 200+ products every week – a soul-crushing task that was eating up 6+ hours of their time.&lt;/p&gt;

&lt;p&gt;"There has to be a better way," I thought. That's when I decided to build a proper Target scraping solution.&lt;/p&gt;

&lt;p&gt;After 3 weeks of development and testing, I ended up with 5 specialized spiders that can extract everything from product details to real-time deals. Here's what I learned along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Target Scraping Challenge: It's Harder Than You Think
&lt;/h2&gt;

&lt;p&gt;Target isn't your average e-commerce site. They've got:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Advanced anti-bot detection&lt;/strong&gt; (Cloudflare + behavioral analysis)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic JavaScript rendering&lt;/strong&gt; for product data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aggressive rate limiting&lt;/strong&gt; (get blocked fast without proxies)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complex page structures&lt;/strong&gt; that change regularly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic URL identifiers&lt;/strong&gt; that change per session&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My first naive attempt? Blocked within 10 requests. 🤦‍♂️&lt;/p&gt;

&lt;p&gt;But here's the thing – Target's data is incredibly valuable for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Price monitoring&lt;/strong&gt; and competitive analysis&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Product research&lt;/strong&gt; for dropshipping/affiliate marketing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Market trend analysis&lt;/strong&gt; across categories&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inventory tracking&lt;/strong&gt; for business intelligence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I knew I had to crack this nut properly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 5-Spider Architecture That Actually Works
&lt;/h2&gt;

&lt;p&gt;Instead of building one massive spider, I broke it down into specialized components:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;target_search.py&lt;/strong&gt; - Product Discovery
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Basic usage
&lt;/span&gt;&lt;span class="n"&gt;scrapy&lt;/span&gt; &lt;span class="n"&gt;crawl&lt;/span&gt; &lt;span class="n"&gt;target_search&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;search_query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;laptop&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;max_products&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perfect for finding products by keywords and building product lists.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;target_product.py&lt;/strong&gt; - Deep Product Data
&lt;/h3&gt;

&lt;p&gt;Extracts 48+ fields per product including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pricing (regular, sale, Target Circle offers)&lt;/li&gt;
&lt;li&gt;Inventory status across fulfillment centers&lt;/li&gt;
&lt;li&gt;Reviews and ratings&lt;/li&gt;
&lt;li&gt;Product identifiers (TCIN, UPC, DPCI)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;target_categories.py&lt;/strong&gt; - Category Mapping
&lt;/h3&gt;

&lt;p&gt;Discovers the entire Target category hierarchy – essential for systematic data collection.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;strong&gt;target_deals.py&lt;/strong&gt; - Promotions &amp;amp; Clearance
&lt;/h3&gt;

&lt;p&gt;This one was tricky. Target's deals page is heavily JavaScript-dependent, but once I cracked it, it became a goldmine for finding discounted products.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. &lt;strong&gt;target_hybrid.py&lt;/strong&gt; - Dynamic Discovery
&lt;/h3&gt;

&lt;p&gt;Combines category exploration with product extraction for comprehensive market analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Challenges &amp;amp; Solutions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Challenge 1: Dynamically Discovering Target's URL Identifiers
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Problem&lt;/strong&gt;: Target doesn't use simple category slugs like &lt;code&gt;/electronics&lt;/code&gt; or &lt;code&gt;/home-garden&lt;/code&gt;. Instead, they use cryptic identifiers that look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://www.target.com/c/deals/-/N-atb3q
https://www.target.com/c/clearance/-/N-5q0ga
https://www.target.com/c/electronics/-/N-5xtg6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those &lt;code&gt;N-xxxxx&lt;/code&gt; codes change per user session and aren't static. Hardcoding them would be a nightmare.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Smart Solution&lt;/strong&gt;: Instead of manual discovery, I built automated identifier extraction directly into the spider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Extract category links and identifiers from Target&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s main pages&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="c1"&gt;# Parse navigation menu for category links
&lt;/span&gt;    &lt;span class="n"&gt;category_links&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;nav a[href*=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/c/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;]::attr(href)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Extract deal section links
&lt;/span&gt;    &lt;span class="n"&gt;deal_links&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a[href*=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deals&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;]::attr(href)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Parse embedded JSON for category mappings
&lt;/span&gt;    &lt;span class="n"&gt;script_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;script:contains(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;navigation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;)::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;script_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;nav_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;categories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nav_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;categories&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;identifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;identifier&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# N-xxxxx code
&lt;/span&gt;            &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.target.com/c/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;slug&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/-/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;identifier&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;follow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Dynamic Discovery in Action&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;discover_deal_identifiers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Automatically find current deal page identifiers&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="c1"&gt;# Target embeds category data in JavaScript
&lt;/span&gt;    &lt;span class="n"&gt;category_script&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;script:contains(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__TGT_DATA__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;)::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;category_script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;category_script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

        &lt;span class="c1"&gt;# Extract deal categories with their current identifiers
&lt;/span&gt;        &lt;span class="n"&gt;deal_categories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dealCategories&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;deal&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;deal_categories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;identifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;navigationId&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Current session identifier
&lt;/span&gt;            &lt;span class="n"&gt;category_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;displayName&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;deal_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.target.com/c/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;deal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;slug&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/-/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;identifier&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;category_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;identifier&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;deal_url&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why This Approach Works&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Session-aware&lt;/strong&gt;: Gets fresh identifiers for each scraping session&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalable&lt;/strong&gt;: Discovers new categories automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintenance-free&lt;/strong&gt;: No hardcoded values that break over time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User-agnostic&lt;/strong&gt;: Works regardless of user session or location&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The Discovery Process&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Step 1: Start from Target's homepage
&lt;/span&gt;&lt;span class="n"&gt;start_urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://www.target.com/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Step 2: Extract navigation structure
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_homepage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Get main navigation links
&lt;/span&gt;    &lt;span class="n"&gt;nav_sections&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[data-test=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;navigation-primary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;] a&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;nav_sections&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;category_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;section&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;::attr(href)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;category_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;section&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/c/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;category_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Category page
&lt;/span&gt;            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;follow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;category_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_category_page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Step 3: Extract identifiers from category pages
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_category_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Parse URL to extract identifier
&lt;/span&gt;    &lt;span class="n"&gt;url_parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;identifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;url_parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;url_parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;N-&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;discovered_identifiers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;category_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;identifier&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach means my spider automatically adapts to Target's changing URL structure without any manual intervention. The &lt;code&gt;target_categories.py&lt;/code&gt; spider now discovers 12+ categories dynamically, and the &lt;code&gt;target_deals.py&lt;/code&gt; spider finds all current deal types automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real Impact&lt;/strong&gt;: This discovery system found deal categories I didn't even know existed, increasing the spider's effectiveness from 4-5 products to 39+ targeted deals per run.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge 2: Getting Past Anti-Bot Detection
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Problem&lt;/strong&gt;: Target blocks scrapers aggressively. Even with user agents and headers, I was getting 403s constantly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Solution&lt;/strong&gt;: Proxy rotation was non-negotiable. After testing several services, I went with ScrapeOps because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://scrapeops.io/app/register/main" rel="noopener noreferrer"&gt;Free tier (1,000 requests)&lt;/a&gt; perfect for development&lt;/li&gt;
&lt;li&gt;95%+ success rate with Target specifically&lt;/li&gt;
&lt;li&gt;Built-in &lt;a href="https://docs.scrapy.org/en/latest/" rel="noopener noreferrer"&gt;Scrapy &lt;/a&gt;integration (no complex setup)&lt;/li&gt;
&lt;li&gt;Real-time monitoring dashboard
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Settings that actually work
&lt;/span&gt;&lt;span class="n"&gt;SCRAPEOPS_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;your-api-key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;  &lt;span class="c1"&gt;# Free from scrapeops.io
&lt;/span&gt;&lt;span class="n"&gt;SCRAPEOPS_PROXY_ENABLED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="n"&gt;CONCURRENT_REQUESTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="c1"&gt;# Start conservative
&lt;/span&gt;&lt;span class="n"&gt;DOWNLOAD_DELAY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Challenge 3: Handling Dynamic Content
&lt;/h3&gt;

&lt;p&gt;Target loads product data via JavaScript. Static HTML scraping gets you maybe 30% of the actual data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My approach&lt;/strong&gt;: Targeted JSON extraction from embedded scripts rather than full browser automation. Faster and more reliable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Extract JSON data from script tags
&lt;/span&gt;&lt;span class="n"&gt;json_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;script:contains(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__TGT_DATA__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;)::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;json_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Parse the embedded product data
&lt;/span&gt;    &lt;span class="n"&gt;product_info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Challenge 4: Field Mapping Hell
&lt;/h3&gt;

&lt;p&gt;Target uses different data structures across pages. A product might have &lt;code&gt;price&lt;/code&gt; in one place and &lt;code&gt;pricing.current&lt;/code&gt; in another.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: Created a robust &lt;code&gt;TargetProductItem&lt;/code&gt; with 48+ fields and intelligent fallback extraction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Smart price extraction with fallbacks
&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;current&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pricing&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;current_retail&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.Price-module__price::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Results That Matter
&lt;/h2&gt;

&lt;p&gt;After optimization, here's what the system delivers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;39 deals&lt;/strong&gt; extracted per run from Target's deals page&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;95%+ success rate&lt;/strong&gt; with proper proxy rotation&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;48+ data fields&lt;/strong&gt; per product with 90%+ completion&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Clean CSV exports&lt;/strong&gt; with timestamps&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Multiple deal types&lt;/strong&gt;: BOGO, percentage discounts, clearance, Target Circle exclusives&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Automatic category discovery&lt;/strong&gt; with dynamic identifier mapping&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The friend I built this for? They've saved 6+ hours per week and discovered 23% more profitable products they were missing before.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Learnings for E-commerce Scraping
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Build for Dynamic Discovery&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Never hardcode identifiers or category URLs. E-commerce sites change these constantly. Build spiders that discover and adapt automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;Proxy Rotation is Essential&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;For any serious e-commerce scraping, especially Target. The free ScrapeOps tier handles development perfectly.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Monitor Success Rates&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;If you're below 90% success rate, something's wrong. Fix it before scaling.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;strong&gt;Structure for Maintenance&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;E-commerce sites change layouts constantly. Build modular spiders that are easy to update.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. &lt;strong&gt;Legal Compliance Matters&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Always check robots.txt and Terms of Service. Respect the website's resources.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. &lt;strong&gt;Start with Navigation Structure&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Parse the main navigation first. It's your roadmap to everything else on the site.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Complete Solution
&lt;/h2&gt;

&lt;p&gt;I've open-sourced the entire scraper suite because I believe good tools should be shared. The codebase includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All 5 production-ready spiders with automated discovery&lt;/li&gt;
&lt;li&gt;Complete setup automation (&lt;code&gt;python setup.py&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Comprehensive documentation with real usage examples&lt;/li&gt;
&lt;li&gt;Professional error handling and logging&lt;/li&gt;
&lt;li&gt;Clean project structure ready for customization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic identifier discovery system&lt;/strong&gt; that adapts to Target's changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;GitHub&lt;/strong&gt;: The full source code is available with detailed setup instructions. Just search for "&lt;a href="https://github.com/Simple-Python-Scrapy-Scrapers/target-scrapy-scraper.git" rel="noopener noreferrer"&gt;target-scrapy-scraper&lt;/a&gt;" – it includes everything you need to get started.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick Start&lt;/strong&gt;: After cloning, you'll need a free ScrapeOps API key for proxy rotation. The setup script guides you through everything.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;How to Scrape Guide: *&lt;/em&gt; Here is the &lt;a href="https://scrapeops.io/websites/target/how-to-scrape-target" rel="noopener noreferrer"&gt;original target guide&lt;/a&gt; I followed.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Website Analyzer: *&lt;/em&gt; This &lt;a href="https://scrapeops.io/websites/target/" rel="noopener noreferrer"&gt;target scraping analyzer&lt;/a&gt; helped me with getting data types, understanding the difficulty, and legalities.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;I'm considering adding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Real-time price change alerts&lt;/li&gt;
&lt;li&gt;Competitor price comparison across multiple retailers&lt;/li&gt;
&lt;li&gt;Integration with Google Sheets/databases&lt;/li&gt;
&lt;li&gt;Mobile app price data&lt;/li&gt;
&lt;li&gt;Enhanced category discovery algorithms&lt;/li&gt;
&lt;li&gt;Automated testing for identifier changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you end up using this scraper or building something similar, I'd love to hear about your experience. E-commerce data extraction is fascinating – there's always more to discover.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What e-commerce scraping challenges have you faced? Drop your experiences in the comments!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>python</category>
      <category>pricetracking</category>
      <category>scrapy</category>
    </item>
    <item>
      <title>Why Google's Anti-Bot System Made Me a Better Developer (A Technical Journey)</title>
      <dc:creator>Noorsimar Singh</dc:creator>
      <pubDate>Thu, 03 Jul 2025 14:00:00 +0000</pubDate>
      <link>https://dev.to/noorsimar/why-googles-anti-bot-system-made-me-a-better-developer-a-technical-journey-5f5c</link>
      <guid>https://dev.to/noorsimar/why-googles-anti-bot-system-made-me-a-better-developer-a-technical-journey-5f5c</guid>
      <description>&lt;h2&gt;
  
  
  The 403 Forbidden error that taught me everything about modern web scraping
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;2025-07-01 02:47:12 &lt;span class="o"&gt;[&lt;/span&gt;scrapy.core.downloader] DEBUG: Retrying &amp;lt;GET https://www.google.com/search?q&lt;span class="o"&gt;=&lt;/span&gt;python&amp;gt; &lt;span class="o"&gt;(&lt;/span&gt;failed 3 &lt;span class="nb"&gt;times&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;: 403 Forbidden
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Staring at this error for the hundredth time, I realized I was approaching Google scraping all wrong. This isn't just another "how to scrape Google" tutorial – it's the story of how reverse-engineering Google's defenses taught me about browser fingerprinting, JavaScript parsing, and building truly resilient systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Google Isn't Playing Fair (And That's Brilliant)
&lt;/h2&gt;

&lt;p&gt;Every developer has been there. You write a beautiful Scrapy spider, test it on a few pages, deploy it confidently... and watch it fail spectacularly in production.&lt;/p&gt;

&lt;p&gt;Google's anti-bot system is a masterclass in defensive engineering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic CSS selectors&lt;/strong&gt; that change between requests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JavaScript-encrypted data&lt;/strong&gt; hidden in plain sight
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser fingerprinting&lt;/strong&gt; that makes basic User-Agent spoofing laughable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limiting algorithms&lt;/strong&gt; that adapt to scraping patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As frustrating as it is, I have to respect the engineering behind it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Technical Breakthrough: Understanding Browser Fingerprinting
&lt;/h2&gt;

&lt;p&gt;The turning point came when I started analyzing real browser requests vs. my scraper's requests. The difference was shocking.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I Was Sending (Amateur Hour):
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;User-Agent&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What Browsers Actually Send:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Accept&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Accept-Language&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;en-US,en;q=0.9&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Accept-Encoding&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gzip, deflate, br&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Cache-Control&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;max-age=0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sec-ch-ua&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="s"&gt;Google Chrome&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;;v=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;119&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Chromium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;;v=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;119&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Not?A_Brand&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;;v=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;24&lt;/span&gt;&lt;span class="sh"&gt;"'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sec-ch-ua-mobile&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;?0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sec-ch-ua-platform&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="s"&gt;Windows&lt;/span&gt;&lt;span class="sh"&gt;"'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Sec-Fetch-Dest&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;document&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Sec-Fetch-Mode&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;navigate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Sec-Fetch-Site&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;none&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Sec-Fetch-User&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;?1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Upgrade-Insecure-Requests&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;sec-ch-ua&lt;/code&gt; and &lt;code&gt;Sec-Fetch-*&lt;/code&gt; headers are security tokens that Chrome automatically includes. Without them, you're basically announcing "I'm a bot."&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep Dive: Reverse Engineering Google Images
&lt;/h2&gt;

&lt;p&gt;Here's where things got really interesting. While everyone focuses on HTML parsing, Google Images stores the real data in JavaScript objects. &lt;/p&gt;

&lt;h3&gt;
  
  
  The Decoy (What Everyone Tries):
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# This gets you placeholder URLs - useless!
&lt;/span&gt;&lt;span class="n"&gt;img_urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;img::attr(src)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# Result: ['data:image/gif;base64,R0lGODlhAQABAIAAAP...']
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Real Deal (What Actually Works):
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="c1"&gt;# Find JavaScript data containing real image URLs
&lt;/span&gt;&lt;span class="n"&gt;scripts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;xpath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;//script/text()&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;script&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;scripts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Google's internal format: [1,[0,"id",["thumb_url",w,h],["full_url",w,h]]
&lt;/span&gt;    &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;\[1,\[0,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[^&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;]+&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,\[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;([^&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;]+)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,[0-9]+,[0-9]+\],\[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;([^&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;]+)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,[0-9]+,[0-9]+\]&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;thumbnail_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;full_image_url&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;thumbnail_url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;thumbnail_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;image_url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;full_image_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# This is the real high-res URL!
&lt;/span&gt;            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;source_domain&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;urlparse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;full_image_url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;netloc&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This revelation changed everything. Instead of fighting Google's HTML, I was reading their internal data structures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a Production-Ready Architecture
&lt;/h2&gt;

&lt;p&gt;After countless iterations, I settled on a three-spider architecture that actually works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Project structure that scales
&lt;/span&gt;&lt;span class="n"&gt;google_search_scraper&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;spiders&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;   &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;google_search&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;      &lt;span class="c1"&gt;# SERP results
&lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;   &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;google_news&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;        &lt;span class="c1"&gt;# News articles  
&lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;   &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="n"&gt;google_images&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;      &lt;span class="c1"&gt;# Real image URLs
&lt;/span&gt;&lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;                  &lt;span class="c1"&gt;# Data models
&lt;/span&gt;&lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="n"&gt;middlewares&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;            &lt;span class="c1"&gt;# Custom logic
&lt;/span&gt;&lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;               &lt;span class="c1"&gt;# Configuration
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Spider 1: Google Search (The Foundation)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Multiple selectors for reliability
&lt;/span&gt;    &lt;span class="n"&gt;search_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;div.tF2Cxc, div.g, div.Gx5Zad&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;search_results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;h3::text, .LC20lb::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a::attr(href)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.VwiC3b::text, .s3v9rd::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;position&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;search_results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;search_results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;search_results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Spider 2: Google News (The Challenge)
&lt;/h3&gt;

&lt;p&gt;Google News was tricky because the selectors change frequently. My solution: adaptive parsing with fallbacks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_news&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Primary selector
&lt;/span&gt;    &lt;span class="n"&gt;news_containers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;div.SoaBEf&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;news_containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Fallback selectors for different layouts
&lt;/span&gt;        &lt;span class="n"&gt;news_containers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;div.Gx5Zad, div.g&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;news_containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GoogleNewsItem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;div.MBeuO::text, h3::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a::attr(href)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;source&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;div.MgUUmf span::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;div.LfVVr::text, span.r0bn4c::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Spider 3: Google Images (The Innovation)
&lt;/h3&gt;

&lt;p&gt;This is where the JavaScript parsing really shines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_image_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;all_scripts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;xpath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;//script/text()&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;script&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;all_scripts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;script&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;BNrT&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;encrypted-tbn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
            &lt;span class="c1"&gt;# Extract real URLs from Google's data structure
&lt;/span&gt;            &lt;span class="n"&gt;pattern1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="s"&gt;BNrT[a-zA-Z0-9]{2}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:\s*\[1,\[0,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[^&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;]+&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,\[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;([^&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;]+)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,[0-9]+,[0-9]+\],\[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;([^&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;]+)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,[0-9]+,[0-9]+\]&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
            &lt;span class="n"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pattern1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;thumbnail_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;full_image_url&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nc"&gt;GoogleImageItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;image_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;full_image_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;thumbnail_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;thumbnail_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;source_domain&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;urlparse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;full_image_url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;netloc&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Infrastructure Problem: Why DIY Proxies Don't Work
&lt;/h2&gt;

&lt;p&gt;Here's the uncomfortable truth: &lt;strong&gt;You can't reliably scrape Google with basic proxies.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I tried everything:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Residential proxy services (inconsistent)&lt;/li&gt;
&lt;li&gt;VPN rotation (too slow)&lt;/li&gt;
&lt;li&gt;Cloud server hopping (gets detected fast)&lt;/li&gt;
&lt;li&gt;Free proxy lists (complete waste of time)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What finally worked was understanding that Google scraping requires &lt;strong&gt;specialized infrastructure&lt;/strong&gt; that understands Google's patterns. After researching various solutions, I found that proxy aggregation services designed specifically for scraping perform significantly better than DIY approaches.&lt;/p&gt;

&lt;p&gt;The key insight: &lt;strong&gt;Don't compete with Google's infrastructure; use professional tools that already solved this problem.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Performance Numbers (That Actually Matter)
&lt;/h2&gt;

&lt;p&gt;After implementing these techniques, here's what I achieved:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Google Search Results&lt;/span&gt;
2025-07-01 11:33:45 &lt;span class="o"&gt;[&lt;/span&gt;scrapy.core.engine] INFO: Spider closed &lt;span class="o"&gt;(&lt;/span&gt;finished&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'item_scraped_count'&lt;/span&gt;: 55, &lt;span class="s1"&gt;'request_count'&lt;/span&gt;: 6, &lt;span class="s1"&gt;'response_status_count/200'&lt;/span&gt;: 6&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Google News Articles  &lt;/span&gt;
2025-07-01 11:35:12 &lt;span class="o"&gt;[&lt;/span&gt;scrapy.core.engine] INFO: Spider closed &lt;span class="o"&gt;(&lt;/span&gt;finished&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'item_scraped_count'&lt;/span&gt;: 56, &lt;span class="s1"&gt;'request_count'&lt;/span&gt;: 6, &lt;span class="s1"&gt;'response_status_count/200'&lt;/span&gt;: 6&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Google Images (Real URLs!)&lt;/span&gt;
2025-07-01 11:39:43 &lt;span class="o"&gt;[&lt;/span&gt;scrapy.core.engine] INFO: Spider closed &lt;span class="o"&gt;(&lt;/span&gt;finished&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'item_scraped_count'&lt;/span&gt;: 300, &lt;span class="s1"&gt;'items_per_minute'&lt;/span&gt;: 782.6, &lt;span class="s1"&gt;'response_status_count/200'&lt;/span&gt;: 6&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;300 real image URLs&lt;/strong&gt; instead of placeholder data URLs. That's the difference between my amateur and professional scraping infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Development Setup That Actually Works
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Python 3.8+ required&lt;/span&gt;
python &lt;span class="nt"&gt;--version&lt;/span&gt;

&lt;span class="c"&gt;# Create isolated environment&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; venv scraper_env
&lt;span class="nb"&gt;source &lt;/span&gt;scraper_env/bin/activate  &lt;span class="c"&gt;# Linux/Mac&lt;/span&gt;
&lt;span class="c"&gt;# scraper_env\Scripts\activate  # Windows&lt;/span&gt;

&lt;span class="c"&gt;# Install dependencies&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;scrapy scrapeops-scrapy scrapeops-scrapy-proxy-sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configuration:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# settings.py - The magic happens here
&lt;/span&gt;&lt;span class="n"&gt;BOT_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;google_search_scraper&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;ROBOTSTXT_OBEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;  &lt;span class="c1"&gt;# Google's robots.txt blocks everything
&lt;/span&gt;
&lt;span class="c1"&gt;# Professional-grade headers
&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_REQUEST_HEADERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Accept&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Accept-Language&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;en-US,en;q=0.9&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sec-ch-ua&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="s"&gt;Google Chrome&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;;v=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;119&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Chromium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;;v=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;119&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Not?A_Brand&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;;v=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;24&lt;/span&gt;&lt;span class="sh"&gt;"'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sec-ch-ua-mobile&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;?0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sec-ch-ua-platform&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="s"&gt;Windows&lt;/span&gt;&lt;span class="sh"&gt;"'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Smart throttling
&lt;/span&gt;&lt;span class="n"&gt;AUTOTHROTTLE_ENABLED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="n"&gt;AUTOTHROTTLE_START_DELAY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;AUTOTHROTTLE_MAX_DELAY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="n"&gt;AUTOTHROTTLE_TARGET_CONCURRENCY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;

&lt;span class="c1"&gt;# This is where the real magic happens - professional proxy integration
&lt;/span&gt;&lt;span class="n"&gt;SCRAPEOPS_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;your-api-key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;  &lt;span class="c1"&gt;# Free tier: 1000 requests
&lt;/span&gt;&lt;span class="n"&gt;SCRAPEOPS_PROXY_ENABLED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

&lt;span class="n"&gt;DOWNLOADER_MIDDLEWARES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;scrapeops_scrapy_proxy_sdk.scrapeops_scrapy_proxy_sdk.ScrapeOpsScrapyProxySdk&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;725&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing Your Setup
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start with a simple test&lt;/span&gt;
scrapy crawl google_search

&lt;span class="c"&gt;# Check the data quality&lt;/span&gt;
&lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 5 data/google_search_&lt;span class="k"&gt;*&lt;/span&gt;.csv

&lt;span class="c"&gt;# Try the advanced features&lt;/span&gt;
scrapy crawl google_images
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you see real image URLs instead of &lt;code&gt;data:image/gif&lt;/code&gt; placeholders, you know it's working.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advanced Debugging Techniques
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Monitor Your Success Rate:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Add this to your spider for real-time monitoring
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;closed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;crawler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stats&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Scraped &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;item_scraped_count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Success rate: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;response_status_count/200&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;request_count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  CSS Selector Debugging:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Use Scrapy shell for live testing&lt;/span&gt;
scrapy shell &lt;span class="s2"&gt;"https://www.google.com/search?q=python"&lt;/span&gt;

&lt;span class="c"&gt;# Test your selectors interactively&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; response.css&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'div.tF2Cxc'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;.get&lt;span class="o"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Proxy Performance Analysis:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Monitor proxy performance in your spider
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;proxy_used&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;proxy&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Response from proxy: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;proxy_used&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What I Learned About Modern Web Scraping
&lt;/h2&gt;

&lt;p&gt;Building this scraper taught me several important lessons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Browser fingerprinting is the new frontier&lt;/strong&gt; - User-Agent headers are just the beginning&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JavaScript parsing often beats HTML parsing&lt;/strong&gt; - The real data might not be in the DOM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Professional infrastructure matters&lt;/strong&gt; - Some problems are worth paying to solve&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adaptive selectors are essential&lt;/strong&gt; - Websites change; your scrapers must adapt&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring is crucial&lt;/strong&gt; - You need real-time feedback on what's working&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Open Source Contribution
&lt;/h2&gt;

&lt;p&gt;I've made the complete scraper available as an open-source project: &lt;a href="https://github.com/Simple-Python-Scrapy-Scrapers/google-scrapy-scraper" rel="noopener noreferrer"&gt;google-search-scrapy-scraper&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's included:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All three production-ready spiders&lt;/li&gt;
&lt;li&gt;Comprehensive configuration examples
&lt;/li&gt;
&lt;li&gt;Debugging tools and monitoring setup&lt;/li&gt;
&lt;li&gt;Regular updates as Google's systems evolve&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why open source?&lt;/strong&gt; Because the scraping community helped me learn, and I want to give back. Plus, Google's defenses evolve constantly – community collaboration makes us all more effective.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Optimization Tips
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Batch Your Requests
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start_requests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;keywords&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;python&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;javascript&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rust&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# Batch related keywords
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;keyword&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;keywords&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# First 2 pages only
&lt;/span&gt;            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.google.com/search?q=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;keyword&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;start=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Smart Error Handling
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Non-200 response: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;div.tF2Cxc&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No results found - possible selector change&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# Trigger alert or fallback logic
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Data Validation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GoogleSearchItem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a::attr(href)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Validate before yielding
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;http&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invalid URL: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Future of Google Scraping
&lt;/h2&gt;

&lt;p&gt;Based on my analysis, here's where I think things are heading:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;AI-powered anti-bot systems&lt;/strong&gt; will become more sophisticated&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JavaScript-heavy interfaces&lt;/strong&gt; will replace static HTML&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Legal frameworks&lt;/strong&gt; will become more defined&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Professional tooling&lt;/strong&gt; will become essential (DIY approaches will fail more often)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Getting Started: Your Action Plan
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clone the repository&lt;/strong&gt; and explore the code structure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set up your development environment&lt;/strong&gt; with proper dependencies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Get a free API key&lt;/strong&gt; for testing (1000 requests should be plenty to start)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run the spiders&lt;/strong&gt; and analyze the output quality&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customize for your use case&lt;/strong&gt; - modify keywords, selectors, output formats&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor and iterate&lt;/strong&gt; - Google changes, so should your scrapers&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Final Thoughts: Why This Matters
&lt;/h2&gt;

&lt;p&gt;Google scraping isn't just about extracting data – it's about understanding modern web architecture, defensive programming, and building resilient systems.&lt;/p&gt;

&lt;p&gt;The techniques I've shared here apply far beyond Google. Browser fingerprinting, JavaScript parsing, and professional infrastructure management are skills that make you a better developer overall.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try the scraper.&lt;/strong&gt; Break it. Improve it. Share your findings with the community. That's how we all get better.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources for Going Deeper
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/Simple-Python-Scrapy-Scrapers/google-scrapy-scraper" rel="noopener noreferrer"&gt;Complete scraper code&lt;/a&gt; - Production-ready implementation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://scrapeops.io/websites/google" rel="noopener noreferrer"&gt;Google scraping analysis&lt;/a&gt; - Technical deep-dive into Google's defenses
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://scrapeops.io/websites/google/how-to-scrape-google" rel="noopener noreferrer"&gt;Advanced techniques guide&lt;/a&gt; - Next-level strategies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What's your experience with scraping challenges?&lt;/strong&gt; Drop a comment – I'm always interested in hearing about novel approaches and war stories from the trenches.&lt;/p&gt;

</description>
      <category>scrapy</category>
      <category>webscraping</category>
      <category>browserfingerprinting</category>
      <category>scrapeops</category>
    </item>
    <item>
      <title>Building a Production-Ready LinkedIn Scraper with Python Scrapy 🐍</title>
      <dc:creator>Noorsimar Singh</dc:creator>
      <pubDate>Wed, 02 Jul 2025 07:47:07 +0000</pubDate>
      <link>https://dev.to/noorsimar/building-a-production-ready-linkedin-scraper-with-python-scrapy-54kb</link>
      <guid>https://dev.to/noorsimar/building-a-production-ready-linkedin-scraper-with-python-scrapy-54kb</guid>
      <description>&lt;p&gt;&lt;em&gt;A complete guide to extracting job data, company profiles, and professional insights at scale&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;I built a comprehensive LinkedIn scraper using Python Scrapy that can extract:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Job listings with pagination (175+ jobs extracted in testing)&lt;/li&gt;
&lt;li&gt;Company profiles with business intelligence&lt;/li&gt;
&lt;li&gt;Professional profiles with experience data&lt;/li&gt;
&lt;li&gt;Anti-bot protection bypass with proxy rotation&lt;/li&gt;
&lt;li&gt;Structured JSON output with automatic validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/Simple-Python-Scrapy-Scrapers/linkedin-scrapy-scraper" rel="noopener noreferrer"&gt;🔗 Full source code on GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;LinkedIn's API is severely limited - you can only access your own data and connected profiles. For comprehensive data extraction (job market analysis, recruitment intelligence, competitive research), web scraping becomes essential.&lt;/p&gt;

&lt;p&gt;But LinkedIn implements aggressive anti-scraping measures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sophisticated bot detection&lt;/li&gt;
&lt;li&gt;Rate limiting and IP blocking
&lt;/li&gt;
&lt;li&gt;JavaScript-heavy dynamic content&lt;/li&gt;
&lt;li&gt;CAPTCHA challenges for suspicious activity&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Solution: Professional Scrapy Architecture
&lt;/h2&gt;

&lt;p&gt;Here's the scraper architecture I built:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;linkedin-scrapy-scraper/
├── linkedin/
│   ├── spiders/
│   │   ├── linkedin_jobs.py              &lt;span class="c"&gt;# Jobs scraper (✅ Working)&lt;/span&gt;
│   │   ├── linkedin_company_profile.py   &lt;span class="c"&gt;# Company data extractor&lt;/span&gt;
│   │   └── linkedin_people_profile.py    &lt;span class="c"&gt;# Profile harvester&lt;/span&gt;
│   ├── middlewares.py                     &lt;span class="c"&gt;# Anti-detection middleware&lt;/span&gt;
│   ├── items.py                          &lt;span class="c"&gt;# Data models&lt;/span&gt;
│   ├── pipelines.py                      &lt;span class="c"&gt;# Data processing&lt;/span&gt;
│   └── settings.py                       &lt;span class="c"&gt;# ScrapeOps integration&lt;/span&gt;
├── data/                                 &lt;span class="c"&gt;# Scraped data output&lt;/span&gt;
└── .gitignore                           &lt;span class="c"&gt;# Clean repo management&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Quick Start
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clone and setup&lt;/span&gt;
git clone https://github.com/Simple-Python-Scrapy-Scrapers/linkedin-scrapy-scraper.git
&lt;span class="nb"&gt;cd &lt;/span&gt;linkedin-scrapy-scraper
python &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; .venv&lt;span class="se"&gt;\S&lt;/span&gt;cripts&lt;span class="se"&gt;\a&lt;/span&gt;ctivate
pip &lt;span class="nb"&gt;install &lt;/span&gt;scrapy scrapeops-scrapy scrapeops-scrapy-proxy-sdk

&lt;span class="c"&gt;# Run the job scraper&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; scrapy crawl linkedin_jobs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deep Dive: Jobs Spider Implementation
&lt;/h2&gt;

&lt;p&gt;The jobs spider is the most reliable since it uses LinkedIn's public job search endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;scrapy&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlencode&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LinkedinJobsSpider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scrapy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Spider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;linkedin_jobs&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

    &lt;span class="c1"&gt;# Auto-save to timestamped JSON Lines
&lt;/span&gt;    &lt;span class="n"&gt;custom_settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;FEEDS&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data/%(name)s_%(time)s.jsonl&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;format&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;jsonlines&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start_requests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Target multiple job categories
&lt;/span&gt;        &lt;span class="n"&gt;queries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;python developer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data scientist&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;devops engineer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;frontend developer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;backend developer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;full stack&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;keywords&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;location&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;United States&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;geoId&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;103644278&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://www.linkedin.com/jobs-guest/jobs/api/seeMoreJobPostings/search?&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;urlencode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;scrapy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_jobs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;page&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_jobs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;jobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.result-card&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Extract comprehensive job data
&lt;/span&gt;            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;job_title&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;h3.result-card__title a::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;company_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;h4.result-card__subtitle a::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;company_location&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.job-result-card__location::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;job_listed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;time.job-result-card__listdate::attr(datetime)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;job_detail_url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;h3.result-card__title a::attr(href)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;company_link&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;h4.result-card__subtitle a::attr(href)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;scraped_at&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;# Smart pagination with limits
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;jobs&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;page&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;next_page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;page&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;keywords&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;location&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;United States&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;geoId&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;103644278&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;next_page&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="n"&gt;next_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://www.linkedin.com/jobs-guest/jobs/api/seeMoreJobPostings/search?&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;urlencode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;scrapy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;next_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_jobs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; 
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;page&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;next_page&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Anti-Detection Strategies
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Proxy Rotation with ScrapeOps
&lt;/h3&gt;

&lt;p&gt;LinkedIn blocks IPs aggressively. ScrapeOps provides residential proxy rotation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# settings.py
&lt;/span&gt;&lt;span class="n"&gt;SCRAPEOPS_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;your_free_api_key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;  &lt;span class="c1"&gt;# Get at scrapeops.io
&lt;/span&gt;&lt;span class="n"&gt;SCRAPEOPS_PROXY_ENABLED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

&lt;span class="n"&gt;DOWNLOADER_MIDDLEWARES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;scrapeops_scrapy_proxy_sdk.scrapeops_scrapy_proxy_sdk.ScrapeOpsScrapyProxySdk&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;725&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Conservative rate limiting
&lt;/span&gt;&lt;span class="n"&gt;CONCURRENT_REQUESTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;DOWNLOAD_DELAY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="n"&gt;RANDOMIZE_DOWNLOAD_DELAY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;
&lt;span class="n"&gt;AUTOTHROTTLE_ENABLED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. User Agent Rotation Middleware
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# middlewares.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;scrapy.downloadermiddlewares.useragent&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UserAgentMiddleware&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RotateUserAgentMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UserAgentMiddleware&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_agent_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;ua&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_agent_list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;User-Agent&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ua&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Advanced Error Handling
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Custom retry middleware for LinkedIn-specific errors
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LinkedInRetryMiddleware&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# LinkedIn's anti-bot response
&lt;/span&gt;            &lt;span class="n"&gt;spider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LinkedIn 999 error for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;challenge&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# CAPTCHA redirect
&lt;/span&gt;            &lt;span class="n"&gt;spider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CAPTCHA challenge detected for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;retry_times&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;retry_req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;retry_req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;retry_times&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;retries&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;retry_req&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Company Profile Spider
&lt;/h2&gt;

&lt;p&gt;Extract business intelligence from LinkedIn company pages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LinkedinCompanySpider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scrapy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Spider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;linkedin_company_profile&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_company&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Extract comprehensive company data
&lt;/span&gt;        &lt;span class="n"&gt;company_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;h1.org-top-card-summary__title::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;industry&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.org-top-card-summary__industry::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;company_size&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.org-about-company-module__company-size-definition-text::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;founded_year&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.org-about-company-module__founded span::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;headquarters&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.org-about-company-module__headquarters span::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.org-about-company-module__description::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;website&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.org-about-company-module__website a::attr(href)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;employee_count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.org-about-company-module__company-staff-count-range::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;follower_count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.org-top-card-summary__follower-count::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;# Extract specialties/keywords
&lt;/span&gt;        &lt;span class="n"&gt;specialties&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.org-about-company-module__specialties dd::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;company_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;specialties&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;specialties&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;

        &lt;span class="c1"&gt;# Extract recent posts/updates
&lt;/span&gt;        &lt;span class="n"&gt;updates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.org-update&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;updates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.org-update__title::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.org-update__time::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.org-update__content::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="n"&gt;company_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;recent_updates&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;updates&lt;/span&gt;

        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;company_data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Professional Profile Spider
&lt;/h2&gt;

&lt;p&gt;Extract detailed professional information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LinkedinPeopleSpider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scrapy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Spider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;linkedin_people_profile&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_profile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Basic profile info
&lt;/span&gt;        &lt;span class="n"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.text-heading-xlarge::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;headline&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.text-body-medium.break-words::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;location&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.text-body-small.inline.t-black--light::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;connections&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.t-black--light .t-bold::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;about&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.pv-about-section .pv-about__summary-text::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;# Extract experience
&lt;/span&gt;        &lt;span class="n"&gt;experience&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;exp&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.pv-profile-section.experience .pv-entity__position-group&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;exp_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.pv-entity__summary-info h3::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;company&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.pv-entity__secondary-title::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;location&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.pv-entity__location span::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;duration&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.pv-entity__date-range span::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.pv-entity__description::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;experience&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exp_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;experience&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;experience&lt;/span&gt;

        &lt;span class="c1"&gt;# Extract education
&lt;/span&gt;        &lt;span class="n"&gt;education&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;edu&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.pv-profile-section.education .pv-entity__position-group&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;edu_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;school&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;edu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.pv-entity__school-name::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;degree&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;edu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.pv-entity__degree-name span::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;field_of_study&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;edu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.pv-entity__fos span::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dates&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;edu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.pv-entity__dates span::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;education&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;edu_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;education&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;education&lt;/span&gt;

        &lt;span class="c1"&gt;# Extract skills
&lt;/span&gt;        &lt;span class="n"&gt;skills&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.pv-skill-category-entity__name span::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;skills&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;skill&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;skill&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;skills&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;skill&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;

        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Data Pipeline &amp;amp; Validation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pipelines.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;itemadapter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ItemAdapter&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;scrapy.exceptions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DropItem&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ValidationPipeline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ItemAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Validate required fields based on spider type
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;spider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;linkedin_jobs&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;job_title&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;company_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;DropItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Missing required fields in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;spider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;linkedin_company_profile&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;DropItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Missing company name in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;spider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;linkedin_people_profile&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;DropItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Missing profile name in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DataCleaningPipeline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ItemAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Clean and normalize text fields
&lt;/span&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field_value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="c1"&gt;# Remove extra whitespace and newlines
&lt;/span&gt;                &lt;span class="n"&gt;cleaned_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field_value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                &lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cleaned_value&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JsonExportPipeline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;open_spider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;spider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;_detailed.json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first_item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;close_spider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first_item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first_item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

        &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ItemAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;asdict&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Performance Metrics &amp;amp; Testing
&lt;/h2&gt;

&lt;p&gt;In my testing environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Jobs Spider Results&lt;/span&gt;
✅ 175+ &lt;span class="nb"&gt;jobs &lt;/span&gt;extracted across 7+ pages
✅ 68KB+ structured data per session  
✅ 100% field extraction success rate
✅ Zero errors with proper rate limiting
✅ Average 1.2 seconds per job with delays

&lt;span class="c"&gt;# File output structure&lt;/span&gt;
data/
├── linkedin_jobs_2024-01-15_14-30-25.jsonl     &lt;span class="c"&gt;# 68KB&lt;/span&gt;
├── linkedin_company_profile_2024-01-15.jsonl   &lt;span class="c"&gt;# 45KB  &lt;/span&gt;
└── linkedin_people_profile_2024-01-15.jsonl    &lt;span class="c"&gt;# 112KB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Scaling for Production
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ScrapeOps Integration
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://scrapeops.io/app/register/main" rel="noopener noreferrer"&gt;ScrapeOps&lt;/a&gt; provides enterprise proxy infrastructure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Free tier: 1,000 requests&lt;/span&gt;
&lt;span class="c"&gt;# Perfect for development and testing&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;scrapeops-scrapy-proxy-sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Production settings
&lt;/span&gt;&lt;span class="n"&gt;SCRAPEOPS_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;your_free_api_key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;  
&lt;span class="n"&gt;SCRAPEOPS_PROXY_ENABLED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="n"&gt;SCRAPEOPS_PROXY_SETTINGS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;country&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;us&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;render_js&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;residential&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Monitoring &amp;amp; Analytics
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Enable ScrapeOps monitoring
&lt;/span&gt;&lt;span class="n"&gt;EXTENSIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;scrapeops_scrapy.extension.ScrapeOpsMonitor&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Real-time scraping metrics:
# - Success/failure rates
# - Response times  
# - Proxy performance
# - Error categorization
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Common Issues &amp;amp; Solutions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  HTTP 999 Errors
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Solution: Enable residential proxies
&lt;/span&gt;&lt;span class="n"&gt;SCRAPEOPS_PROXY_SETTINGS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;residential&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  JavaScript Content Loading
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Solution: Use Scrapy-Splash
&lt;/span&gt;&lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;scrapy&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;splash&lt;/span&gt;
&lt;span class="c1"&gt;# Or enable JS rendering in ScrapeOps
&lt;/span&gt;&lt;span class="n"&gt;SCRAPEOPS_PROXY_SETTINGS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;render_js&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Rate Limiting
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Conservative approach for LinkedIn
&lt;/span&gt;&lt;span class="n"&gt;DOWNLOAD_DELAY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="n"&gt;RANDOMIZE_DOWNLOAD_DELAY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;
&lt;span class="n"&gt;CONCURRENT_REQUESTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Real-World Applications
&lt;/h2&gt;

&lt;p&gt;This scraper has been used for:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Job Market Analysis&lt;/strong&gt; 📊
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Analyze salary trends by location/technology
&lt;/span&gt;&lt;span class="n"&gt;jobs_df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data/linkedin_jobs.jsonl&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;salary_trends&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jobs_df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;location&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;technology&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;salary&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mean&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;job_title&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;reset_index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Recruitment Intelligence&lt;/strong&gt; 👥
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Track competitor hiring patterns
&lt;/span&gt;&lt;span class="n"&gt;company_jobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jobs_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;jobs_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;company_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;isin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;competitors&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="n"&gt;hiring_velocity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;company_jobs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;company_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Lead Generation&lt;/strong&gt; 🎯
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Identify growing companies in your sector
&lt;/span&gt;&lt;span class="n"&gt;growing_companies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;companies_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;companies_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;employee_count_change&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; 
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;companies_df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;industry&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Software&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Security &amp;amp; Legal Considerations
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Implement respectful scraping
&lt;/span&gt;&lt;span class="n"&gt;ROBOTSTXT_OBEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;  &lt;span class="c1"&gt;# Respect robots.txt
&lt;/span&gt;&lt;span class="n"&gt;DOWNLOAD_DELAY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;     &lt;span class="c1"&gt;# Don't overwhelm servers
&lt;/span&gt;
&lt;span class="c1"&gt;# Data privacy compliance
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PrivacyPipeline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Remove PII for GDPR compliance
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;phone&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;phone&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clone the repo&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Simple-Python-Scrapy-Scrapers/linkedin-scrapy-scraper.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Get free ScrapeOps API key&lt;/strong&gt;: &lt;a href="https://scrapeops.io/app/register/main" rel="noopener noreferrer"&gt;scrapeops.io/app/register/main&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Run your first scrape&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; scrapy crawl linkedin_jobs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Analyze the data&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data/linkedin_jobs_*.jsonl&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🔄 &lt;strong&gt;Real-time monitoring&lt;/strong&gt; with job alerts&lt;/li&gt;
&lt;li&gt;🤖 &lt;strong&gt;ML integration&lt;/strong&gt; for salary prediction&lt;/li&gt;
&lt;li&gt;📊 &lt;strong&gt;Dashboard creation&lt;/strong&gt; with Streamlit/Dash
&lt;/li&gt;
&lt;li&gt;🌐 &lt;strong&gt;Multi-region support&lt;/strong&gt; with geo-targeted proxies&lt;/li&gt;
&lt;li&gt;📈 &lt;strong&gt;Advanced analytics&lt;/strong&gt; with trend detection&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;📚 &lt;a href="https://github.com/Simple-Python-Scrapy-Scrapers/linkedin-scrapy-scraper" rel="noopener noreferrer"&gt;Complete GitHub Repository&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🆓 &lt;a href="https://scrapeops.io/app/register/main" rel="noopener noreferrer"&gt;Free ScrapeOps API Key&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📖 &lt;a href="https://scrapeops.io/websites/linkedin" rel="noopener noreferrer"&gt;LinkedIn Scraping Analyzer&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📖 &lt;a href="https://scrapeops.io/websites/linkedin/how-to-scrape-linkedin" rel="noopener noreferrer"&gt;Original LinkedIn Scraping Guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🐍 &lt;a href="https://docs.scrapy.org/" rel="noopener noreferrer"&gt;Scrapy Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Found this helpful?&lt;/strong&gt; ⭐ Star the repository and follow for more web scraping tutorials!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Questions?&lt;/strong&gt; Drop them in the comments below 👇&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want to collaborate?&lt;/strong&gt; Open an issue or submit a PR!&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>linkedin</category>
      <category>datascience</category>
      <category>scrapy</category>
    </item>
    <item>
      <title>From 500+ Lines to 150: How I Refactored My Amazon Scraper Into a Clean Scrapy Project</title>
      <dc:creator>Noorsimar Singh</dc:creator>
      <pubDate>Wed, 25 Jun 2025 15:22:09 +0000</pubDate>
      <link>https://dev.to/noorsimar/from-500-lines-to-150-how-i-refactored-my-amazon-scraper-into-a-clean-scrapy-project-14o3</link>
      <guid>https://dev.to/noorsimar/from-500-lines-to-150-how-i-refactored-my-amazon-scraper-into-a-clean-scrapy-project-14o3</guid>
      <description>&lt;h2&gt;
  
  
  The Messy Beginning
&lt;/h2&gt;

&lt;p&gt;Like many developers, I started Amazon scraping with a single Python file. What began as a "quick script" quickly turned into 500+ lines of spaghetti code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The old way - amazon_spider_old.py (don't do this!)
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bs4&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BeautifulSoup&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fake_useragent&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UserAgent&lt;/span&gt;

&lt;span class="c1"&gt;# 50+ lines of proxy management
# 100+ lines of parsing logic
# 200+ lines of error handling
# 150+ lines of data export
# Complete nightmare to maintain!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sound familiar? Here's how I transformed this mess into a clean, maintainable Scrapy project.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Pain Points of DIY Scraping
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Problem #1: IP Blocks Everywhere
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# My hacky solution (don't judge!)
&lt;/span&gt;&lt;span class="n"&gt;proxies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://proxy1:8000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://proxy2:8000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="c1"&gt;# ... manually managing 20+ proxies
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proxies&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;proxies&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;proxies&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]})&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;  &lt;span class="c1"&gt;# Prayer-driven development 🙏
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Problem #2: Fragile Selectors
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# This broke every week
&lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;soup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.a-price-whole&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;soup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.a-offscreen&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;soup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.a-price&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Not found&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# 😭
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Problem #3: Zero Monitoring
&lt;/h3&gt;

&lt;p&gt;When scraping failed (and it failed often), I had no idea why:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Was it the proxy?&lt;/li&gt;
&lt;li&gt;Did Amazon change their layout?&lt;/li&gt;
&lt;li&gt;Network issues?&lt;/li&gt;
&lt;li&gt;Rate limiting?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Debugging was pure guesswork.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Scrapy Transformation
&lt;/h2&gt;

&lt;p&gt;After one too many 3 AM debugging sessions, I decided to rebuild using Scrapy. Here's the before/after:&lt;/p&gt;

&lt;h3&gt;
  
  
  Before: Single Monolithic File
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;amazon_spider_old.py (534 lines)
├── Proxy management (manual) 
├── User agent rotation (manual)
├── Request retries (manual)
├── Data parsing (fragile)
├── CSV export (buggy)
└── Error handling (prayer-based)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  After: Clean Scrapy Architecture
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;amazon-scrapy-scraper/
├── amazon_scraper/
│   ├── spiders/
│   │   ├── amazon_search.py      (72 lines)
│   │   └── amazon_product.py     (78 lines)
│   ├── settings.py               (31 lines)
│   └── data/                     (auto-generated)
├── requirements.txt              (3 lines)
└── README.md                     (comprehensive docs)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result: 500+ lines → 150 lines total! 🎉&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Architectural Decisions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Single Responsibility Spiders
&lt;/h3&gt;

&lt;p&gt;Instead of one spider doing everything, I created focused spiders:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Search Spider&lt;/strong&gt; (&lt;code&gt;amazon_search.py&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AmazonSearchSpider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scrapy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Spider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;amazon_search&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start_requests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;keyword_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ipad&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;keyword&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;keyword_list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://www.amazon.com/s?k=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;keyword&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;scrapy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Extract search results
&lt;/span&gt;        &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;div[data-component-type=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;s-search-result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;h2 a span::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.a-price-whole::text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;h2 a::attr(href)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Product Spider&lt;/strong&gt; (&lt;code&gt;amazon_product.py&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AmazonProductSpider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scrapy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Spider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;amazon_product&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_product_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Detailed product extraction
&lt;/span&gt;        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#productTitle::text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extract_price&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;features&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extract_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;images&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extract_images&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Configuration Over Code
&lt;/h3&gt;

&lt;p&gt;All settings in one place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# settings.py
&lt;/span&gt;&lt;span class="n"&gt;BOT_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;amazon_scraper&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;ROBOTSTXT_OBEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;span class="n"&gt;CONCURRENT_REQUESTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="c1"&gt;# ScrapeOps Integration (game changer!)
&lt;/span&gt;&lt;span class="n"&gt;SCRAPEOPS_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;your-api-key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;SCRAPEOPS_PROXY_ENABLED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

&lt;span class="n"&gt;DOWNLOADER_MIDDLEWARES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;scrapeops_scrapy_proxy_sdk.scrapeops_scrapy_proxy_sdk.ScrapeOpsScrapyProxySdk&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;725&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Auto-export to CSV
&lt;/span&gt;&lt;span class="n"&gt;FEEDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data/%(name)s_%(time)s.csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;format&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Built-in Superpowers
&lt;/h3&gt;

&lt;p&gt;Scrapy + ScrapeOps gives you so much for free:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scrapy provides:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automatic retries&lt;/strong&gt; with exponential backoff&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrent requests&lt;/strong&gt; with rate limiting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Robust selectors&lt;/strong&gt; with fallbacks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple export formats&lt;/strong&gt; (CSV, JSON, XML)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Middleware system&lt;/strong&gt; for custom logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;ScrapeOps provides:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Proxy rotation&lt;/strong&gt; and IP management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time monitoring&lt;/strong&gt; dashboard&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Request success tracking&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The ScrapeOps Game Changer
&lt;/h2&gt;

&lt;p&gt;The biggest improvement wasn't Scrapy itself—it was integrating ScrapeOps:&lt;/p&gt;

&lt;h3&gt;
  
  
  Before: Manual Proxy Hell
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 50+ lines of proxy management code
&lt;/span&gt;&lt;span class="n"&gt;proxies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_proxy_list&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;current_proxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_next_proxy&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;current_proxy&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current_proxy&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proxies&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;current_proxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Wait and pray
&lt;/span&gt;    &lt;span class="n"&gt;proxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;proxies&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;current_proxy&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;current_proxy&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;proxy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  After: One Line Integration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# settings.py
&lt;/span&gt;&lt;span class="n"&gt;SCRAPEOPS_PROXY_ENABLED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! ScrapeOps handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Proxy rotation&lt;/li&gt;
&lt;li&gt;IP geolocation&lt;/li&gt;
&lt;li&gt;User agent rotation&lt;/li&gt;
&lt;li&gt;Request success monitoring&lt;/li&gt;
&lt;li&gt;Automatic retries&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Monitoring Dashboard
&lt;/h3&gt;

&lt;p&gt;The ScrapeOps dashboard shows real-time metrics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request success rates&lt;/li&gt;
&lt;li&gt;Response times&lt;/li&gt;
&lt;li&gt;Error patterns&lt;/li&gt;
&lt;li&gt;Bandwidth usage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So.. No more guessing what went wrong.&lt;/p&gt;




&lt;h2&gt;
  
  
  Performance Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Old Script&lt;/th&gt;
&lt;th&gt;New Scrapy Project&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Lines of Code&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;534&lt;/td&gt;
&lt;td&gt;150&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Success Rate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~70%&lt;/td&gt;
&lt;td&gt;~95%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Maintainability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Nightmare&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Debugging Time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hours&lt;/td&gt;
&lt;td&gt;Minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scalability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Unlimited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monitoring&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Full dashboard&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Real-World Usage Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Quick Product Search
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Search for iPads&lt;/span&gt;
scrapy crawl amazon_search

&lt;span class="c"&gt;# Output: data/amazon_search_2024-01-15T10-30-45.csv&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Detailed Product Analysis
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Get comprehensive product data&lt;/span&gt;
scrapy crawl amazon_product

&lt;span class="c"&gt;# Output includes features, images, variants&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Custom Keywords
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Edit amazon_search.py
&lt;/span&gt;&lt;span class="n"&gt;keyword_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mechanical keyboards&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gaming mice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;monitors&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Lessons for Fellow Developers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Don't Reinvent the Wheel
&lt;/h3&gt;

&lt;p&gt;I wasted months building retry logic and export functionality that Scrapy provides out of the box, plus proxy rotation that ScrapeOps handles.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Monitoring is Essential
&lt;/h3&gt;

&lt;p&gt;Without ScrapeOps monitoring, I was flying blind. Now I can optimize based on real data.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Architecture Matters
&lt;/h3&gt;

&lt;p&gt;Two focused spiders are infinitely more maintainable than one giant spider.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Start with Free Tiers
&lt;/h3&gt;

&lt;p&gt;ScrapeOps free tier (1,000 requests/month) is perfect for development and small projects.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started (5 Minutes Setup)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clone and setup:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   git clone https://github.com/Simple-Python-Scrapy-Scrapers/amazon-scrapy-scraper.git
   &lt;span class="nb"&gt;cd &lt;/span&gt;amazon-scrapy-scraper
   python &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
   &lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate  &lt;span class="c"&gt;# or .venv\Scripts\Activate.ps1 on Windows&lt;/span&gt;
   pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Get free ScrapeOps API key:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visit &lt;a href="https://scrapeops.io/app/register/main" rel="noopener noreferrer"&gt;ScrapeOps.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;1,000 free requests/month&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Configure and run:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="c"&gt;# Add API key to amazon_scraper/settings.py&lt;/span&gt;
   &lt;span class="nb"&gt;cd &lt;/span&gt;amazon_scraper
   scrapy crawl amazon_search
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;This architecture scales beautifully. I'm already planning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Review scraper&lt;/strong&gt; for sentiment analysis&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Price tracking&lt;/strong&gt; with alerts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-marketplace&lt;/strong&gt; support (eBay, Walmart)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API endpoints&lt;/strong&gt; for real-time data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The clean foundation makes adding features straightforward.&lt;/p&gt;




&lt;h2&gt;
  
  
  Repository &amp;amp; Resources
&lt;/h2&gt;

&lt;p&gt;🔗 &lt;strong&gt;Full source code:&lt;/strong&gt; &lt;a href="https://github.com/Simple-Python-Scrapy-Scrapers/amazon-scrapy-scraper" rel="noopener noreferrer"&gt;amazon-scrapy-scraper&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;📚 &lt;strong&gt;Helpful resources:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.scrapy.org/en/latest/intro/tutorial.html" rel="noopener noreferrer"&gt;Scrapy Tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://scrapeops.io/docs/proxy/" rel="noopener noreferrer"&gt;ScrapeOps Proxy Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://scrapeops.io/websites/amazon" rel="noopener noreferrer"&gt;Amazon Scraping Analyzer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://scrapeops.io/monitoring-scheduling/" rel="noopener noreferrer"&gt;ScrapeOps Monitoring&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Discussion
&lt;/h2&gt;

&lt;p&gt;Have you refactored similar projects? What architectural patterns work best for web scraping?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Drop a comment with:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your biggest scraping pain points&lt;/li&gt;
&lt;li&gt;Scrapy tips and tricks&lt;/li&gt;
&lt;li&gt;Alternative tools you've tried&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's help each other build better scrapers!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;P.S. - Web scraping should always respect robots.txt and rate limits. This project is for educational purposes.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>scrapy</category>
      <category>scrapeops</category>
      <category>python</category>
    </item>
  </channel>
</rss>
