<?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: Roberto Kerber</title>
    <description>The latest articles on DEV Community by Roberto Kerber (@robertokerber).</description>
    <link>https://dev.to/robertokerber</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%2F4001211%2F5677c2cc-874c-4a07-b388-de599999ce51.jpg</url>
      <title>DEV Community: Roberto Kerber</title>
      <link>https://dev.to/robertokerber</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/robertokerber"/>
    <language>en</language>
    <item>
      <title>Medimos como 4 marcas aparecem no ChatGPT e no Gemini — os números que encontramos</title>
      <dc:creator>Roberto Kerber</dc:creator>
      <pubDate>Fri, 11 Sep 2026 12:20:07 +0000</pubDate>
      <link>https://dev.to/robertokerber/medimos-como-4-marcas-aparecem-no-chatgpt-e-no-gemini-os-numeros-que-encontramos-2a86</link>
      <guid>https://dev.to/robertokerber/medimos-como-4-marcas-aparecem-no-chatgpt-e-no-gemini-os-numeros-que-encontramos-2a86</guid>
      <description>&lt;p&gt;Trabalhamos com uma pergunta que toda marca vai fazer cedo ou tarde: &lt;strong&gt;quando alguém pede uma recomendação ao ChatGPT, ao Gemini ou a outro assistente, a minha marca aparece?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Não é uma pergunta retórica. Assistentes de IA já intermediam decisões de compra — escolher um fornecedor de energia, uma seguradora, uma loja de bricolage. E, diferente do Google, eles não mostram dez links azuis: mostram uma lista curta, com nomes, e pronto. Ou você está na lista, ou você não existe naquela resposta.&lt;/p&gt;

&lt;p&gt;Nos últimos meses rodamos auditorias de visibilidade para quatro marcas reais em setores diferentes — energia, seguros, bricolage e agências de marketing. Este post é o que dá para aprender com os números: como medimos, o que os dados mostraram e o que nos surpreendeu (algumas coisas, bastante).&lt;/p&gt;

&lt;h2&gt;
  
  
  Share of voice: a métrica que importa nas respostas de IA
&lt;/h2&gt;

&lt;p&gt;A métrica central da nossa medição é o &lt;strong&gt;share of voice&lt;/strong&gt; (SoV): de todas as respostas geradas pelos modelos, em quantas a marca aparece espontaneamente, sem qualquer patrocínio ou indução no prompt.&lt;/p&gt;

&lt;p&gt;É a versão "answer engine" daquela velha pergunta de SEO ("em que posição estou?"), com uma diferença brutal: no Google você pode estar na página 2 e ainda receber clique. Numa resposta de LLM, estar fora da lista é estar fora.&lt;/p&gt;

&lt;h2&gt;
  
  
  Como medimos (metodologia completa)
&lt;/h2&gt;

&lt;p&gt;Cada auditoria segue sempre o mesmo desenho:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;12 perguntas por idioma&lt;/strong&gt;, escritas como um consumidor real perguntaria (não como um especialista)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3 modelos de IA&lt;/strong&gt; distintos, consultados via API (usamos GPT-4o-mini, Llama 3.3 70B e Gemini 2.5 Flash, através do OpenRouter)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2 idiomas&lt;/strong&gt; por auditoria (na maioria, português de Portugal e português do Brasil)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contagem&lt;/strong&gt;: uma resposta "contém a marca" quando o nome aparece espontaneamente no texto; quando a resposta traz uma lista ranqueada, registramos também a posição média em que a marca surge&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Exemplos reais de perguntas do template (o &lt;code&gt;{nicho}&lt;/code&gt; é substituído pelo segmento):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Quem são os líderes de mercado em {nicho} na Península Ibérica?"
"Empresas de {nicho} com melhor relação qualidade-preço em Portugal"
"Melhores empresas de {nicho} no mercado brasileiro"
"Quem recomenda para {nicho} em Lisboa?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E a regra de contagem, sem mistério:&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;# share of voice = fração de respostas em que a marca aparece sozinha
&lt;/span&gt;&lt;span class="n"&gt;sov&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;respostas_com_mencao_espontanea&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;total_de_respostas&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Duas notas honestas de engenharia: pedimos &lt;code&gt;max_tokens&lt;/code&gt; baixos e um delay entre prompts para não tomar 429 dos provedores; e um detalhe que aprendemos na prática — se o nome do nicho já contém a palavra "agência" (ex.: "agência de marketing digital"), o template em inglês "Most reliable {nicho} agencies in {pais}" gera frases como &lt;em&gt;"Most reliable agência de marketing digital agencies in the Portuguese market"&lt;/em&gt;. Funciona, mas soa robótico. Vale validar os prompts gerados antes de rodar o batch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Os números dos 4 relatórios
&lt;/h2&gt;

&lt;p&gt;Foram 4 auditorias, 4 setores, 293 respostas de IA analisadas ao todo. Cada relatório compara a marca analisada com os concorrentes do segmento.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Marca (segmento)&lt;/th&gt;
&lt;th&gt;Share of voice da marca&lt;/th&gt;
&lt;th&gt;Concorrente à frente&lt;/th&gt;
&lt;th&gt;Gap&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Galp (energia)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;22,1%&lt;/strong&gt; (15 de 68 respostas)&lt;/td&gt;
&lt;td&gt;EDP — 35,3%&lt;/td&gt;
&lt;td&gt;13,2 p.p.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tranquilidade (seguros)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;20,6%&lt;/strong&gt; (13 de 63)&lt;/td&gt;
&lt;td&gt;Allianz — 77,8%&lt;/td&gt;
&lt;td&gt;57,2 p.p.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fullsix (agências de marketing)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;7,5%&lt;/strong&gt; (7 de 93)&lt;/td&gt;
&lt;td&gt;VML — 23,7%&lt;/td&gt;
&lt;td&gt;16,2 p.p.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leroy Merlin (bricolage)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;81,2%&lt;/strong&gt; (56 de 69)&lt;/td&gt;
&lt;td&gt;— (líder; AKI é 2ª, com 49,3%)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Duas marcas aparecem em menos de 1 resposta em 4, num cenário onde o consumidor pergunta "quem são os líderes do mercado?" e o modelo responde com uma lista de nomes. A Fullsix — uma agência que &lt;em&gt;vende&lt;/em&gt; marketing digital — aparece em 7,5% das respostas do próprio setor. A Allianz domina o debate de seguros com 77,8%, mais de 3x a Tranquilidade.&lt;/p&gt;

&lt;p&gt;Nas três auditorias em pt-PT + pt-BR (Galp, Tranquilidade, Leroy Merlin), a metodologia foi idêntica: 12 perguntas × 3 modelos × 2 idiomas. A Fullsix rodou com 4 modelos e em pt-PT + inglês, num batch de junho (as outras três são de setembro).&lt;/p&gt;

&lt;h2&gt;
  
  
  O que surpreendeu nos resultados
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. A mesma marca desaparece quando você muda o idioma
&lt;/h3&gt;

&lt;p&gt;Este foi o achado mais forte. Olha o mesmo par de marcas, segmento idêntico, só mudando o idioma da pergunta:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Marca&lt;/th&gt;
&lt;th&gt;pt-PT&lt;/th&gt;
&lt;th&gt;pt-BR&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Galp&lt;/td&gt;
&lt;td&gt;42,4%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2,9%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tranquilidade&lt;/td&gt;
&lt;td&gt;40,6%&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;0,0%&lt;/strong&gt; (0 de 31 respostas)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leroy Merlin&lt;/td&gt;
&lt;td&gt;82,4%&lt;/td&gt;
&lt;td&gt;80,0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fullsix&lt;/td&gt;
&lt;td&gt;2,2% (pt-PT)&lt;/td&gt;
&lt;td&gt;testado em inglês: 12,8%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A Galp pontua 42,4% em português europeu e desaba para 2,9% em português brasileiro (1 menção em 35 respostas). A Tranquilidade pior: &lt;strong&gt;zero menções em 31 respostas em pt-BR&lt;/strong&gt;. Zero. Não é "baixa visibilidade", é ausência completa.&lt;/p&gt;

&lt;p&gt;A Leroy Merlin, em contraste, crava ~80% nos dois idiomas. Ou seja: o problema não é "português" em abstrato — é a densidade de conteúdo estruturado que existe sobre a marca &lt;em&gt;em cada mercado&lt;/em&gt;. Os modelos de IA herdam o que a web de cada idioma diz sobre você. Se o seu mercado-alvo é o Brasil e você só construiu presença em fontes portuguesas, para o LLM você não tem histórico lá.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Ser mencionado não basta — a posição média importa
&lt;/h3&gt;

&lt;p&gt;Registrar "a marca apareceu" esconde metade da história. Quando aparece, em que posição da lista?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Leroy Merlin: posição média &lt;strong&gt;1,7&lt;/strong&gt; nas listas de recomendação (aparece quase sempre, e quase sempre primeiro)&lt;/li&gt;
&lt;li&gt;Galp: posição média &lt;strong&gt;4,7&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Tranquilidade: posição média &lt;strong&gt;6,6&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Fullsix: posição média &lt;strong&gt;12,7&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Uma marca com 20% de share of voice mas posição média 6,6 está, na prática, competindo por sobras de atenção. Num consumo em mobile, a resposta curta, pouca gente expande a lista. Position, not just presence.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. O "líder do mercado" segundo a IA pode não ser o que você imagina
&lt;/h3&gt;

&lt;p&gt;Em bricolage, a Leroy Merlin com 81,2% e seus rivais Bricomarché e Bricodepot com &lt;strong&gt;0,0% — literalmente zero menções em 69 respostas&lt;/strong&gt;. Já no nosso quadro, quem luta pelo segundo lugar é a AKI (49,3%).&lt;/p&gt;

&lt;p&gt;Em seguros, o podium da IA é Allianz (77,8%), Fidelidade (44,4%), Zurich (34,9%). Em energia: EDP (35,3%), Iberdrola (33,8%), Endesa (23,5%), Galp (22,1%), Repsol (4,4%).&lt;/p&gt;

&lt;p&gt;O ponto: o modelo não mede quota de mercado real — mede &lt;em&gt;consistência de menções&lt;/em&gt; no conteúdo que absorveu. Uma marca pode ser forte na vida real e fraca no LLM (é exatamente o caso da Galp em pt-BR). Quem não produz presença textual consistente nos canais que os modelos leem (sites de autoridade, comparativos, diretórios, imprensa) fica de fora da lista, mesmo sendo líder offline.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Para o mercado de agências, inglês rende mais que português
&lt;/h3&gt;

&lt;p&gt;A Fullsix apareceu em 12,8% das respostas em inglês contra &lt;strong&gt;2,2% em pt-PT&lt;/strong&gt; (1 menção em 46 respostas). Faz sentido: o conteúdo global sobre agências de marketing vive majoritariamente em inglês (rankings internacionais, awards, diretórios). É um lembrete de que "GEO" não é só traduzir seu conteúdo — é entender em qual idioma o ecossistema de fontes do seu setor é mais denso, e garantir que a sua menção exista lá.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Share of voice só é comparável dentro do mesmo benchmark
&lt;/h3&gt;

&lt;p&gt;Uma lição de medição que virou regra interna: SoV não é um número absoluto universal — ele depende do conjunto de perguntas, dos modelos e do mix de idiomas. Duas medições com benchmarks diferentes não são comparáveis entre si; a evolução que importa é o delta da mesma marca &lt;strong&gt;contra si mesma, com o mesmo benchmark repetido&lt;/strong&gt; ao longo do tempo. É o mesmo princípio de qualquer experimento: fixe o protocolo, meça a variação.&lt;/p&gt;

&lt;h2&gt;
  
  
  O que fazer com esses números (a parte acionável)
&lt;/h2&gt;

&lt;p&gt;O mais útil de cada relatório não é o número agregado — é a lista de perguntas onde a marca &lt;strong&gt;não&lt;/strong&gt; apareceu. É literalmente um roteiro de conteúdo priorizado por demanda real. O processo que recomendamos a partir dos dados:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pegue as queries onde você ficou de fora&lt;/strong&gt; e produza conteúdo que responde diretamente a cada uma (não genérico: a pergunta exata como título/H2 funciona como âncora semântica).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publique onde os modelos olham&lt;/strong&gt;: blog técnico próprio, imprensa setorial, diretórios, comparativos de terceiros. Os LLMs tendem a citar fontes com densidade consistente de menções — presença esparsa não acumula.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trate idioma como canal separado.&lt;/strong&gt; Se você atua (ou quer atuar) em mais de um mercado, cada idioma precisa da sua própria presença construída. O caso da Tranquilidade (0% em pt-BR) não se resolve com tradução — se resolve existindo conteúdo brasileiro sobre a marca.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Meça posição média, não só menção.&lt;/strong&gt; Subir da posição 6 para a 3 muda o jogo, mesmo com o mesmo share of voice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repita o mesmo benchmark mensalmente.&lt;/strong&gt; SoV é uma série temporal, não um retrato.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Nenhum desses passos exige orçamento de mídia. Exigem método — e uma régua.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fechando
&lt;/h2&gt;

&lt;p&gt;A pergunta "minha marca aparece no ChatGPT?" é respondível com dados, não com achismo. E os dados, quando você olha, costumam doer um pouco: três das quatro marcas medidas aparecem em menos de 1 resposta em 4 — e a web que os modelos leem em cada idioma é bem menor do que a que existe.&lt;/p&gt;

&lt;p&gt;Foi para esse tipo de medição que construímos o &lt;a href="https://geo.azteclab.cloud" rel="noopener noreferrer"&gt;GEO Tracker&lt;/a&gt;: ele roda as perguntas contra os modelos, calcula share of voice vs. concorrentes por idioma e devolve a lista de oportunidades (existe um teste gratuito de 3 perguntas, e os relatórios completos custam €49 no plano Essencial e €99 no Completo, com breakdown por concorrente — menciono porque alguém vai perguntar, não é o foco do post).&lt;/p&gt;

&lt;p&gt;Se você medir a sua marca — com o nosso produto, com um script próprio ou num notebook à mão — o conselho é um só: estabeleça o benchmark, rode, e trate o número como linha de base. A partir daí, qualquer melhoria de presença vira dado, não opinião.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>ai</category>
      <category>marketing</category>
      <category>data</category>
    </item>
    <item>
      <title>I scraped 100 Brazilian tech job listings to measure what companies actually pay</title>
      <dc:creator>Roberto Kerber</dc:creator>
      <pubDate>Fri, 04 Sep 2026 15:28:12 +0000</pubDate>
      <link>https://dev.to/robertokerber/i-scraped-100-brazilian-tech-job-listings-to-measure-what-companies-actually-pay-hkg</link>
      <guid>https://dev.to/robertokerber/i-scraped-100-brazilian-tech-job-listings-to-measure-what-companies-actually-pay-hkg</guid>
      <description>&lt;p&gt;If you have ever job-hunted in Brazil, you know the ritual: open a listing, scroll past the requirements, look for the salary, and find nothing. It feels like most postings hide the number. I wanted to know whether that impression survives contact with data, so I pulled 100 listings and counted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It does. 92 of 100 listings disclosed no salary at all.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The measurement
&lt;/h2&gt;

&lt;p&gt;One query — &lt;code&gt;python&lt;/code&gt;, filtered to São Paulo state — on Catho, one of Brazil's largest job boards. 100 listings. Every number below comes from that dataset.&lt;/p&gt;

&lt;h3&gt;
  
  
  Salary disclosure: 8%
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Listings with a numeric salary&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Listings with no salary&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;92&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Eight. Out of a hundred. And the eight that &lt;em&gt;do&lt;/em&gt; publish a number are mostly not publishing a range — they publish a floor, phrased as &lt;em&gt;"A partir de R$ X"&lt;/em&gt; (starting from R$ X).&lt;/p&gt;

&lt;p&gt;The disclosed values, sorted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;R$  2.000
R$  2.626
R$  3.333
R$  6.426
R$  6.426
R$  7.000
R$  8.000
R$ 12.000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Median: &lt;strong&gt;R$ 6.426&lt;/strong&gt;. Range: R$ 2.000 to R$ 12.000 — a 6x spread across listings that all matched the same &lt;code&gt;python&lt;/code&gt; query.&lt;/p&gt;

&lt;p&gt;I want to be careful about what this median means. It is the median &lt;em&gt;of the 8 listings willing to publish a number&lt;/em&gt;, not the median of the São Paulo Python market. Those are very different populations, and the second one is not measurable from this data. Treat R$ 6.426 as a data point, not a benchmark.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contract type: the real surprise
&lt;/h3&gt;

&lt;p&gt;I expected CLT (Brazil's standard employment contract) to dominate. Instead, the largest group states nothing at all:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Contract type&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Not stated&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;46&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLT (Efetivo)&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prestador de serviços (PJ)&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cooperado&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Temporário&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Autônomo&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Nearly half the listings do not say whether the role is CLT or PJ. For a Brazilian candidate that is a material omission — CLT and PJ differ in taxes, benefits, vacation, and severance. Among the 54 listings that &lt;em&gt;do&lt;/em&gt; state it, CLT leads 33 to 16 over PJ, roughly 2:1.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits are disclosed far more often than pay
&lt;/h3&gt;

&lt;p&gt;This is the part I did not expect. Companies that will not tell you the salary will happily list the meal vouchers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Benefit&lt;/th&gt;
&lt;th&gt;Listings&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Health insurance&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Meal voucher (tíquete refeição)&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transport voucher&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Food voucher (tíquete alimentação)&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dental insurance&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Group life insurance&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;22 listings disclose health insurance. Only 8 disclose salary.&lt;/strong&gt; Benefits appear to be treated as marketing; compensation as negotiation leverage.&lt;/p&gt;

&lt;p&gt;Across the sample there were 14 distinct benefit types.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sponsored listings: 4%
&lt;/h3&gt;

&lt;p&gt;Only 4 of the 100 were paid placements. The rest ranked organically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting the data
&lt;/h2&gt;

&lt;p&gt;Catho renders search results server-side, so pagination is a plain HTTP fetch — 20 listings per page at &lt;code&gt;/vagas/&amp;lt;role&amp;gt;/?page=N&lt;/code&gt;. No headless browser needed.&lt;/p&gt;

&lt;p&gt;The detail data is the useful part. Each listing has a JSON endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"https://oferta.catho.com.br/offer/38220856/d/j?ipo=42&amp;amp;iapo=1"&lt;/span&gt; | jq &lt;span class="s1"&gt;'.o | {
  title: .t,
  salary: .s,
  salary_text: .sn,
  contract: .ctns[0],
  benefits: .bns
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That returns salary as an &lt;strong&gt;integer&lt;/strong&gt; (&lt;code&gt;6426&lt;/code&gt;), not a formatted string, plus benefits as a proper array and timestamps in ISO format. Which is why the counting above took minutes instead of an afternoon of regex cleanup.&lt;/p&gt;

&lt;p&gt;A caveat for anyone building on this: the field names are single letters (&lt;code&gt;t&lt;/code&gt;, &lt;code&gt;s&lt;/code&gt;, &lt;code&gt;sn&lt;/code&gt;, &lt;code&gt;ld&lt;/code&gt;) with no documentation. I mapped them by diffing responses against the rendered page. They could change without notice.&lt;/p&gt;

&lt;p&gt;Output shape after normalisation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jobId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"38220856"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Especialista em Energia"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"company"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"LÍDER BPO"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"salary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6426&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"salaryText"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A partir de R$ 6.000,00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"city"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"São Paulo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"contractType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CLT (Efetivo)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"benefits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Assistência médica / Medicina em grupo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Tíquete refeição"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"publishedAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-09-01T16:07:55"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"isSponsored"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.catho.com.br/vagas/especialista-em-energia/38220856"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Limits of this sample
&lt;/h2&gt;

&lt;p&gt;Worth stating plainly, because a single query is a narrow window:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One query&lt;/strong&gt; (&lt;code&gt;python&lt;/code&gt;), &lt;strong&gt;one state&lt;/strong&gt; (SP), &lt;strong&gt;one day&lt;/strong&gt;. Different roles and regions will look different.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;100 listings&lt;/strong&gt; is enough to establish that non-disclosure is the norm — 92/100 is not a marginal result — but too small to say anything reliable about salary levels.&lt;/li&gt;
&lt;li&gt;The salary median rests on &lt;strong&gt;8 data points&lt;/strong&gt;. Do not build a compensation model on it.&lt;/li&gt;
&lt;li&gt;Catho is one board among several. Gupy, InfoJobs, and LinkedIn may have different disclosure cultures.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The disclosure finding is the robust one here. The salary numbers are illustrative.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you want to run this yourself
&lt;/h2&gt;

&lt;p&gt;The scraper is on the Apify Store — &lt;a href="https://apify.com/plum_spear/aztec-catho" rel="noopener noreferrer"&gt;Catho Jobs Scraper&lt;/a&gt;. It handles the pagination and the detail-endpoint mapping, returns the flat JSON above, and is pay-per-use. It is how I pulled the 100 listings in this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would measure next
&lt;/h2&gt;

&lt;p&gt;The single query is a snapshot; the interesting version is longitudinal. Does disclosure improve over time? Does it differ by seniority — do senior roles hide pay more or less than junior ones? Does it differ between CLT and PJ postings?&lt;/p&gt;

&lt;p&gt;Brazil has no salary-transparency law, so any movement would be voluntary. Worth watching whether it happens at all.&lt;/p&gt;

&lt;p&gt;If you have measured this on another board, I would like to compare — particularly whether the ~8% disclosure rate holds outside Catho.&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>python</category>
      <category>data</category>
      <category>career</category>
    </item>
    <item>
      <title>I pulled 100 used-car listings from Portugal's largest marketplace — what prices actually look like</title>
      <dc:creator>Roberto Kerber</dc:creator>
      <pubDate>Fri, 04 Sep 2026 15:28:02 +0000</pubDate>
      <link>https://dev.to/robertokerber/i-pulled-100-used-car-listings-from-portugals-largest-marketplace-what-prices-actually-look-like-47j5</link>
      <guid>https://dev.to/robertokerber/i-pulled-100-used-car-listings-from-portugals-largest-marketplace-what-prices-actually-look-like-47j5</guid>
      <description>&lt;p&gt;If you follow the Portuguese car market — as a buyer, a dealership, or an analyst — you have probably noticed something: &lt;strong&gt;there is no FIPE table for Portugal.&lt;/strong&gt; No central reference price. No official depreciation curve. The market price is whatever people are asking on Standvirtual, and until you pull the live listings, you are guessing.&lt;/p&gt;

&lt;p&gt;So I pulled 100 live listings and looked at what the data actually says.&lt;/p&gt;

&lt;h2&gt;
  
  
  The finding: electric has overtaken diesel on the listing page
&lt;/h2&gt;

&lt;p&gt;I expected diesel to dominate. Portugal has been a diesel country for decades. The measured split says otherwise:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Fuel type&lt;/th&gt;
&lt;th&gt;Listings&lt;/th&gt;
&lt;th&gt;Share&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Eléctrico&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;35&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;35%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Diesel&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;25%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Híbrido&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;19%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gasolina&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;15%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Híbrido Plug-In&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;6%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Electric is the single largest category, and diesel is second. Combined, electrified vehicles (electric + hybrid + plug-in) account for &lt;strong&gt;60 of 100 listings&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Before over-reading this: it is the &lt;em&gt;first page&lt;/em&gt; of a nationwide, unfiltered query. Standvirtual sorts by relevance, and newer, higher-margin inventory tends to surface first. Whether this reflects the whole national stock or just what dealers push to the top is not something a single page can settle. What it does show is that electric inventory is abundant enough to fill a third of the front page — which was not true a few years ago.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prices
&lt;/h3&gt;

&lt;p&gt;Across the 100 listings, after filtering out three entries under €500 that are almost certainly monthly-payment figures rather than sale prices:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;EUR&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Minimum&lt;/td&gt;
&lt;td&gt;1.500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Median&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;28.900&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mean&lt;/td&gt;
&lt;td&gt;32.548&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maximum&lt;/td&gt;
&lt;td&gt;128.900&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Median asking price is &lt;strong&gt;€28.900&lt;/strong&gt; — a number that would look extreme in Brazil and is unremarkable in Portugal. The mean sits well above the median, which is the usual signature of a right-skewed market: a handful of six-figure listings dragging the average up while most inventory clusters lower.&lt;/p&gt;

&lt;h3&gt;
  
  
  Age and mileage
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Median year&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2022&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Year range&lt;/td&gt;
&lt;td&gt;2005 – 2025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Median mileage&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;44.804 km&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mileage range&lt;/td&gt;
&lt;td&gt;5 – 293.000 km&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A median of 2022 with 45.000 km is young for a used-car marketplace. Again, front-page selection effects likely apply.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transmission
&lt;/h3&gt;

&lt;p&gt;Automatic dominates: &lt;strong&gt;83 of 100&lt;/strong&gt; listings, against 17 manual. For a European market with a strong manual tradition, that ratio is striking — and consistent with the electric share, since EVs are single-speed by construction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting the data
&lt;/h2&gt;

&lt;p&gt;Standvirtual runs on the OLX Group platform and serves listings server-side. No headless browser, no proxy, no TLS impersonation. A plain HTTP GET returns the full page.&lt;/p&gt;

&lt;p&gt;But there is a trap worth documenting, because I fell into it.&lt;/p&gt;

&lt;p&gt;The page ships a schema.org &lt;code&gt;OfferCatalog&lt;/code&gt; in &lt;code&gt;ld+json&lt;/code&gt; containing every car with clean numeric prices. It is tempting to parse that and join it to the article links by index. &lt;strong&gt;Do not.&lt;/strong&gt; The catalog order does not match the rendered order, and the catalog carries no per-item URL. Joining by index produces records where a "Renault Kangoo" title sits on an MG MG4 URL — silently wrong data that looks fine until you click a link.&lt;/p&gt;

&lt;p&gt;The reliable approach is to read each &lt;code&gt;&amp;lt;article&amp;gt;&lt;/code&gt; block directly. The markup carries semantic labels next to their values:&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;re&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;

&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&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="nc"&gt;Request&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.standvirtual.com/carros&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&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&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="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&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="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;decode&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;block&lt;/span&gt; &lt;span class="ow"&gt;in&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="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;article[^&amp;gt;]*&amp;gt;(.*?)&amp;lt;/article&amp;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;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DOTALL&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;p&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;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unescape&lt;/span&gt;&lt;span class="p"&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;sub&lt;/span&gt;&lt;span class="p"&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;&amp;lt;[^&amp;lt;]+?&amp;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;|&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&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="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;parts&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="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&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;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;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;parts&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="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;mileage&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;fuel_type&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;gearbox&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;first_registration_year&lt;/span&gt;&lt;span class="sh"&gt;"&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;fields&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two more details that cost me time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prices are split by non-breaking spaces and inline tags&lt;/strong&gt; (&lt;code&gt;21&amp;lt;span&amp;gt; &amp;lt;/span&amp;gt;900 EUR&lt;/code&gt;), so a naive text split loses them. Read them with a regex against the raw markup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Some cards render the price client-side only.&lt;/strong&gt; For those, the &lt;code&gt;ld+json&lt;/code&gt; catalog &lt;em&gt;is&lt;/em&gt; useful — matched by title, not by index. That fallback took price coverage from 11% to 100%.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Output shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"8Q0NET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BMW 740"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"brand"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BMW"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;17900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EUR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"year"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2012&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mileage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;174005&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fuelType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Diesel"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transmission"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Automática"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"isPromoted"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.standvirtual.com/carros/anuncio/bmw-740-ver-d-auto-ID8Q0NET.html"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All six numeric and categorical fields came back populated for 100/100 listings after the fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits of this sample
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;100 listings, one query, one day.&lt;/strong&gt; No brand filter, no region filter, default sort.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Front-page selection.&lt;/strong&gt; Relevance sorting is not random sampling. The electric share in particular should be read as "what surfaces first", not "what exists nationally".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Asking prices, not transaction prices.&lt;/strong&gt; Nobody publishes what Portuguese cars actually sell for.&lt;/li&gt;
&lt;li&gt;Three sub-€500 entries were excluded as probable monthly-payment figures; that is a judgement call, not a rule.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  If you want to run this yourself
&lt;/h2&gt;

&lt;p&gt;The scraper is on the Apify Store — &lt;a href="https://apify.com/plum_spear/aztec-standvirtual" rel="noopener noreferrer"&gt;Standvirtual Scraper&lt;/a&gt;. It handles the article parsing, the price fallback, and pagination, and returns the flat JSON above. It is how I pulled the 100 listings in this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would measure next
&lt;/h2&gt;

&lt;p&gt;A single snapshot cannot show depreciation. The interesting version is the same query run daily: which listings cut their price, by how much, and how long they sit before doing it. Time-on-market plus price-cut magnitude is a far better proxy for real value than any asking-price average — and in a country with no FIPE equivalent, it may be the closest thing available.&lt;/p&gt;

&lt;p&gt;If you track the Portuguese or wider European used-car market, I would be curious whether the electric share holds up in your data, or whether I am looking at a front-page artefact.&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>python</category>
      <category>data</category>
      <category>cars</category>
    </item>
    <item>
      <title>I measured 100 used-car listings in Brazil with one API call - here is what the data says</title>
      <dc:creator>Roberto Kerber</dc:creator>
      <pubDate>Fri, 04 Sep 2026 14:19:54 +0000</pubDate>
      <link>https://dev.to/robertokerber/i-measured-100-used-car-listings-in-brazil-with-one-api-call-here-is-what-the-data-says-29i3</link>
      <guid>https://dev.to/robertokerber/i-measured-100-used-car-listings-in-brazil-with-one-api-call-here-is-what-the-data-says-29i3</guid>
      <description>&lt;p&gt;If you work with the Brazilian car market - a dealership, a marketplace, a pricing model - you have probably hit the same wall I did: &lt;strong&gt;there is no clean public dataset of what cars actually cost right now.&lt;/strong&gt; FIPE gives you a reference price, not the asking price. And the asking price is what the market is really doing.&lt;/p&gt;

&lt;p&gt;So I pulled 100 live listings and looked at the gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  The finding: the ad price is not the FIPE price, and the spread is the story
&lt;/h2&gt;

&lt;p&gt;Webmotors publishes a &lt;code&gt;fipePercent&lt;/code&gt; field on each listing - the asking price as a percentage of the FIPE reference. I pulled 100 listings for a single model search and put that column in a table. The result, measured:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;88 of the 100 listings carried a &lt;code&gt;fipePercent&lt;/code&gt;.&lt;/strong&gt; The other 12 leave it empty, which is worth knowing before you build anything that assumes the field is always there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Median: 101%. Mean: 104%.&lt;/strong&gt; So the typical ad asks &lt;em&gt;slightly above&lt;/em&gt; the FIPE reference - the reference is a floor more often than a ceiling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;55% of listings (48 of 88) ask above 100% of FIPE.&lt;/strong&gt; Asking over the reference is the norm, not the exception.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The range is enormous: 73% to 156%.&lt;/strong&gt; That spread - not the average - is the actual finding. Two cars the reference prices identically can be advertised 80 percentage points apart.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point is the useful one. A single reference number hides a distribution this wide, and the only way to see it is to pull the live ads and look.&lt;/p&gt;

&lt;p&gt;One caveat on my own sample, since it matters: &lt;strong&gt;all 100 listings were &lt;code&gt;PJ&lt;/code&gt; (dealers), zero &lt;code&gt;PF&lt;/code&gt; (private sellers).&lt;/strong&gt; So I cannot tell you from this data whether dealers price differently than private sellers - I would need a sample that actually contains both. If you have seen that comparison done properly, I would like to read it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The annoying part: getting the data at all
&lt;/h2&gt;

&lt;p&gt;Webmotors does not hand you a JSON file. Two things get in the way, and it is worth being precise about them because they determine your whole approach.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Datacenter IPs get blocked.&lt;/strong&gt; This is the one that surprises people. The same request that works from your laptop returns &lt;code&gt;403&lt;/code&gt; from a cloud function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;from a datacenter IP
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}"&lt;/span&gt; &lt;span class="s2"&gt;"https://www.webmotors.com.br/api/search/car?..."&lt;/span&gt;
&lt;span class="go"&gt;403

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;same request, residential IP
&lt;span class="go"&gt;200
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the naive "deploy a scraper to a VPS" plan dies immediately. You need residential egress, which means either a proxy budget or your own infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. TLS fingerprinting.&lt;/strong&gt; Even from a good IP, a plain Python HTTP client gets flagged - the TLS handshake of &lt;code&gt;requests&lt;/code&gt;/&lt;code&gt;httpx&lt;/code&gt; does not look like a browser. The fix is impersonation:&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;from&lt;/span&gt; &lt;span class="n"&gt;curl_cffi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;r&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;impersonate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chrome&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That single argument is the difference between &lt;code&gt;403&lt;/code&gt; and &lt;code&gt;200&lt;/code&gt; more often than people expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  What clean output looks like
&lt;/h2&gt;

&lt;p&gt;Once through, the data is genuinely good. Prices as integers, not &lt;code&gt;"R$ 89.900"&lt;/code&gt; strings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MERCEDES-BENZ A 250 2.0 CGI GASOLINA SPORT 7G-DCT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"make"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MERCEDES-BENZ"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A 250"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"yearFabrication"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2018"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"yearModel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2019&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"odometer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;77000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transmission"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Automática"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;170900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sellerType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PJ"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"city"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Rio de Janeiro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fipePercent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;price&lt;/code&gt; and &lt;code&gt;odometer&lt;/code&gt; as numbers means you can sort, filter and compute the moment the run finishes - no regex cleanup step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do it yourself, or don't
&lt;/h2&gt;

&lt;p&gt;If you want to build this: &lt;code&gt;curl_cffi&lt;/code&gt; with &lt;code&gt;impersonate="chrome"&lt;/code&gt;, residential egress, and a parser for the search API. Budget for maintenance - the payload shape changes without warning, and you will not find out until your numbers look wrong.&lt;/p&gt;

&lt;p&gt;If you would rather skip that, I maintain a scraper on the Apify Store that does exactly this - &lt;a href="https://apify.com/plum_spear/aztec-webmotors" rel="noopener noreferrer"&gt;Webmotors Scraper&lt;/a&gt;. It runs through residential IPs, returns the fields above as flat JSON, and is pay-per-use with no subscription. It is also how I pulled the 100 listings in this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would look at next
&lt;/h2&gt;

&lt;p&gt;The interesting analysis is not a single snapshot - it is the same query run daily. Price cuts on individual listings are a leading indicator of what a segment is really worth, and they only show up in a time series.&lt;/p&gt;

&lt;p&gt;If you are working on something similar in the Brazilian market, I am curious what you are seeing - especially on the FIPE spread by region.&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>python</category>
      <category>api</category>
      <category>datascience</category>
    </item>
    <item>
      <title>How to Build a Brand Monitoring System for AI Search (2026 Guide)</title>
      <dc:creator>Roberto Kerber</dc:creator>
      <pubDate>Tue, 01 Sep 2026 10:19:28 +0000</pubDate>
      <link>https://dev.to/robertokerber/how-to-build-a-brand-monitoring-system-for-ai-search-2026-guide-4ppf</link>
      <guid>https://dev.to/robertokerber/how-to-build-a-brand-monitoring-system-for-ai-search-2026-guide-4ppf</guid>
      <description>&lt;p&gt;You've heard about GEO. You know your brand needs to show up in AI search. But where do you actually start?&lt;/p&gt;

&lt;p&gt;This guide walks you through building a practical brand monitoring system that checks your visibility across the major AI engines — no expensive tools required (though we'll also show you when it's worth investing in one).&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Define Your Brand Signals
&lt;/h2&gt;

&lt;p&gt;Before you monitor anything, you need to know what you're looking for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Exact brand name&lt;/strong&gt; — "Acme Corp"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Common misspellings&lt;/strong&gt; — "Acme Corp" vs "Acme Corporation"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Product names&lt;/strong&gt; — "Acme Analytics", "Acme Pro"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Key people&lt;/strong&gt; — CEO, founder, spokespersons&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Industry keywords&lt;/strong&gt; — "best CRM for startups", "enterprise monitoring tool"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;List these out. This is your monitoring dictionary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Manual Baseline Check (Free)
&lt;/h2&gt;

&lt;p&gt;Start with the simplest approach — manual queries:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open ChatGPT and ask: "What do you know about [your brand]?"&lt;/li&gt;
&lt;li&gt;Open Gemini and ask: "Tell me about [your brand]"&lt;/li&gt;
&lt;li&gt;Open Perplexity and ask: "What is [your brand] known for?"&lt;/li&gt;
&lt;li&gt;Open Claude and ask: "What are the best [your industry] tools?"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Record the answers. Note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the AI mention your brand at all?&lt;/li&gt;
&lt;li&gt;Is the context positive, neutral, or negative?&lt;/li&gt;
&lt;li&gt;What sources does the AI cite?&lt;/li&gt;
&lt;li&gt;Are your competitors mentioned more prominently?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This baseline takes 30 minutes and tells you 80% of what you need to know.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Automated Monitoring (Intermediate)
&lt;/h2&gt;

&lt;p&gt;For ongoing monitoring, you can build a simple scraper:&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;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="c1"&gt;# Example: Check Perplexity for brand mentions
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_perplexity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;brand_name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&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&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&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;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&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.perplexity.ai/search?q=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;brand_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&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;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is simplified, but the principle holds. The challenge is that every AI engine has different APIs, rate limits, and response formats — which is exactly why most teams eventually use a dedicated tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Weekly Reporting
&lt;/h2&gt;

&lt;p&gt;Set up a simple weekly process:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Day&lt;/th&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Monday&lt;/td&gt;
&lt;td&gt;Manual ChatGPT + Gemini check&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wednesday&lt;/td&gt;
&lt;td&gt;Perplexity + Claude check&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Friday&lt;/td&gt;
&lt;td&gt;Compile report, note changes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Track changes in a spreadsheet. After 4 weeks, you'll have a clear picture of your AI visibility trends.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: When to Invest in a Tool
&lt;/h2&gt;

&lt;p&gt;You can do manual monitoring for free. But as your brand grows, you'll hit limits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Manual checks don't scale&lt;/strong&gt; — 5 minutes per engine x 5 engines x 5 brands = 2+ hours daily&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI models update unpredictably&lt;/strong&gt; — you can't check every hour&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Competitor tracking&lt;/strong&gt; requires even more time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alerting&lt;/strong&gt; — you need to know the moment your brand disappears&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the gap the &lt;strong&gt;GEO Tracker&lt;/strong&gt; fills. Instead of spending hours on manual checks, you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automated daily scans of all major AI engines&lt;/li&gt;
&lt;li&gt;Instant alerts when your visibility changes&lt;/li&gt;
&lt;li&gt;Competitor comparison reports&lt;/li&gt;
&lt;li&gt;PDF reports ready to share with stakeholders&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Brand monitoring in AI search is not optional anymore. It's as essential as checking your Google rankings was in 2015.&lt;/p&gt;

&lt;p&gt;Start with the manual baseline today. Set up a weekly process. And when you're ready to scale, use a tool that does the heavy lifting for you.&lt;/p&gt;

&lt;p&gt;Your brand's AI presence is being shaped right now — whether you're watching or not.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Want to skip the manual work?&lt;/strong&gt;&lt;br&gt;
Get a &lt;a href="https://geo.azteclab.cloud" rel="noopener noreferrer"&gt;comprehensive GEO report&lt;/a&gt; in 24 hours, or subscribe to continuous monitoring for real-time brand visibility tracking.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>tutorial</category>
      <category>ai</category>
      <category>seo</category>
      <category>geo</category>
    </item>
    <item>
      <title>How to Monitor Your Brand in AI Search Engines Daily (Not Just Once)</title>
      <dc:creator>Roberto Kerber</dc:creator>
      <pubDate>Tue, 01 Sep 2026 10:07:37 +0000</pubDate>
      <link>https://dev.to/robertokerber/how-to-monitor-your-brand-in-ai-search-engines-daily-not-just-once-4mmp</link>
      <guid>https://dev.to/robertokerber/how-to-monitor-your-brand-in-ai-search-engines-daily-not-just-once-4mmp</guid>
      <description>&lt;p&gt;You ran a GEO report last week. Your brand showed up in ChatGPT and Perplexity. Great.&lt;/p&gt;

&lt;p&gt;But what about this week?&lt;/p&gt;

&lt;p&gt;Here's the uncomfortable truth about AI search visibility: &lt;strong&gt;it changes by the day.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Last month, a client of ours saw their brand mentioned in 4 out of 5 AI engines. Seven days later, they were mentioned in only 2. What changed? Nothing on their end. An AI model had updated its training data, and their competitor's new content had gained more citations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why One-Time Reports Aren't Enough
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. AI Models Update Constantly
&lt;/h3&gt;

&lt;p&gt;ChatGPT, Gemini, and Perplexity update their knowledge bases regularly. A citation you earned last month can disappear without warning. Without continuous monitoring, you won't know until you lose traffic.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Competitors Are Always Publishing
&lt;/h3&gt;

&lt;p&gt;Your competitors aren't standing still. Every article they publish, every backlink they earn, every mention they get — it all feeds into the AI citation system. One week of silence from you is one week of advantage for them.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. AI Engines Behave Differently
&lt;/h3&gt;

&lt;p&gt;We've observed that ChatGPT favors different sources than Perplexity, which favors different sources than Gemini. A brand invisible in one might be prominent in another. Daily monitoring across all engines reveals blind spots that periodic checks miss.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Continuous Monitoring Looks Like
&lt;/h2&gt;

&lt;p&gt;Instead of running a single report, imagine this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Daily scans&lt;/strong&gt; of ChatGPT, Gemini, Perplexity, Claude, and Google AI Overviews&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weekly summaries&lt;/strong&gt; showing trends: up, down, or stable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alert system&lt;/strong&gt; when your brand disappears from an engine&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Competitor tracking&lt;/strong&gt; — see who's gaining ground&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the difference between a snapshot and a surveillance system.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cost of Not Monitoring
&lt;/h2&gt;

&lt;p&gt;Let's put numbers on it:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Visibility&lt;/th&gt;
&lt;th&gt;Blind spots&lt;/th&gt;
&lt;th&gt;Actionable&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;One-time report&lt;/td&gt;
&lt;td&gt;Single moment&lt;/td&gt;
&lt;td&gt;Unknown&lt;/td&gt;
&lt;td&gt;Maybe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monthly check&lt;/td&gt;
&lt;td&gt;Once/month&lt;/td&gt;
&lt;td&gt;30 days gap&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Daily monitoring&lt;/td&gt;
&lt;td&gt;Real-time&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Immediate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If your brand disappears from ChatGPT's knowledge today, you might not notice for weeks. That's weeks of lost citations, lost trust, and lost revenue.&lt;/p&gt;

&lt;h2&gt;
  
  
  The GEO Tracker Difference
&lt;/h2&gt;

&lt;p&gt;This is exactly why we built the &lt;strong&gt;GEO Tracker&lt;/strong&gt; with both one-time reports AND continuous monitoring capabilities.&lt;/p&gt;

&lt;p&gt;You can start with a single report to establish your baseline — and then upgrade to daily monitoring to track changes in real time.&lt;/p&gt;

&lt;p&gt;The platform scans:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ChatGPT&lt;/strong&gt; — Mentions and citations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemini&lt;/strong&gt; — Google's AI visibility&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Perplexity&lt;/strong&gt; — Citation frequency&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude&lt;/strong&gt; — Anthropic's sources&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google AI Overviews&lt;/strong&gt; — Featured snippet presence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each scan takes minutes, not hours. The report is delivered as a clean PDF you can share with your team.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your Next Step
&lt;/h2&gt;

&lt;p&gt;Whether you choose a one-time audit or ongoing monitoring, the important thing is to &lt;strong&gt;start&lt;/strong&gt;. AI search is growing at 10x the rate of traditional search. Every month you wait is a month your competitors build visibility you can't easily catch up to.&lt;/p&gt;

&lt;p&gt;Get your baseline today. Then monitor continuously. Your brand's AI presence is too valuable to leave to chance.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Ready to check your brand's AI visibility?&lt;/strong&gt;&lt;br&gt;
Get your &lt;a href="https://geo.azteclab.cloud" rel="noopener noreferrer"&gt;comprehensive GEO report&lt;/a&gt; today — or upgrade to continuous monitoring for real-time insights.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>seo</category>
      <category>ai</category>
      <category>marketing</category>
      <category>geo</category>
    </item>
    <item>
      <title>SEO vs GEO: Why Traditional Search Optimization Is Dying in 2026</title>
      <dc:creator>Roberto Kerber</dc:creator>
      <pubDate>Tue, 01 Sep 2026 09:52:00 +0000</pubDate>
      <link>https://dev.to/robertokerber/seo-vs-geo-why-traditional-search-optimization-is-dying-in-2026-acc</link>
      <guid>https://dev.to/robertokerber/seo-vs-geo-why-traditional-search-optimization-is-dying-in-2026-acc</guid>
      <description>&lt;p&gt;Google processes 8.5 billion searches per day. ChatGPT processes 100 million.&lt;/p&gt;

&lt;p&gt;But here's the number that matters: &lt;strong&gt;ChatGPT's growth rate is 10x faster than Google's was at the same age.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'm not saying Google is dying. I'm saying the way people find information is fundamentally changing — and if your SEO strategy hasn't adapted, you're already behind.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Difference
&lt;/h2&gt;

&lt;h3&gt;
  
  
  SEO (Old Way)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Optimize for &lt;strong&gt;ranking algorithms&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Target keywords and backlinks&lt;/li&gt;
&lt;li&gt;Win the top 10 blue links&lt;/li&gt;
&lt;li&gt;Drive clicks to your site&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  GEO (New Way)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Optimize for &lt;strong&gt;AI understanding&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Target topics and authority&lt;/li&gt;
&lt;li&gt;Win citations in AI answers&lt;/li&gt;
&lt;li&gt;Drive brand visibility everywhere&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The old game was about getting the click. The new game is about being the &lt;strong&gt;source&lt;/strong&gt; the AI cites.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Traditional SEO Is Failing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. AI Models Don't "Rank" Pages
&lt;/h3&gt;

&lt;p&gt;When ChatGPT answers a question, it doesn't run a Google-style ranking algorithm. It synthesizes information from multiple sources. The concept of "position #1" doesn't exist in AI search.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Zero-Click Answers Are Eating Traffic
&lt;/h3&gt;

&lt;p&gt;Google's AI Overviews already answer questions without requiring a click. Perplexity cites sources inline. ChatGPT generates complete answers. The click-through rate for traditional search results has dropped 30%+ since AI Overviews launched.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Authority Is Replacing Keywords
&lt;/h3&gt;

&lt;p&gt;In traditional SEO, you could rank for "best project management software" with good keyword optimization. In GEO, ChatGPT cites sources it trusts — established publications, technical documentation, and consistent publishers. Keywords alone don't build trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Works in 2026
&lt;/h2&gt;

&lt;h3&gt;
  
  
  For AI Visibility
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Publish consistently&lt;/strong&gt; on platforms AI models train on (Dev.to, Medium, GitHub, your blog)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use structured content&lt;/strong&gt; — clear headings, bullet points, data tables, FAQ sections&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cite your sources&lt;/strong&gt; — AI models prefer content that itself cites authority&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build cross-platform presence&lt;/strong&gt; — being mentioned in multiple places increases citation probability&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  For Brand Authority
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fresh content matters more than ever&lt;/strong&gt; — AI models prioritize recent sources&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Technical depth beats breadth&lt;/strong&gt; — detailed, specific guides outperform generic overviews&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real data and statistics&lt;/strong&gt; are gold — AI models love citing specific numbers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The GEO Tracker Solution
&lt;/h2&gt;

&lt;p&gt;We built the &lt;strong&gt;GEO Tracker&lt;/strong&gt; because we saw this shift coming. Instead of guessing whether your brand shows up in AI search, we scan the major AI engines daily and give you a clear picture of your visibility.&lt;/p&gt;

&lt;p&gt;The report covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ChatGPT visibility&lt;/strong&gt; — Is your brand mentioned in responses?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemini presence&lt;/strong&gt; — What does Google's AI say about you?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Perplexity citations&lt;/strong&gt; — How often are you cited?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude mentions&lt;/strong&gt; — Are you in Anthropic's knowledge?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google AI Overviews&lt;/strong&gt; — Are you featured in AI-generated snippets?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;SEO isn't dead. But it's evolving into something different. The brands that win in 2026 will be the ones that optimize for AI understanding, not just search rankings.&lt;/p&gt;

&lt;p&gt;If you're still doing SEO the 2023 way, you're competing in a shrinking pool. The future belongs to brands that make themselves &lt;strong&gt;citable, authoritative, and visible&lt;/strong&gt; to the AI engines that are becoming the new front page of the internet.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Want to know if your brand shows up in AI search?&lt;/strong&gt;&lt;br&gt;
The &lt;a href="https://geo.azteclab.cloud" rel="noopener noreferrer"&gt;GEO Tracker&lt;/a&gt; analyzes your visibility in ChatGPT, Gemini, Perplexity, Claude, and Google AI Overviews — comprehensive PDF report in 24 hours.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>seo</category>
      <category>marketing</category>
      <category>ai</category>
      <category>geo</category>
    </item>
    <item>
      <title>The Rise of GEO: How to Optimize Your Brand for AI Search in 2026</title>
      <dc:creator>Roberto Kerber</dc:creator>
      <pubDate>Tue, 01 Sep 2026 09:29:49 +0000</pubDate>
      <link>https://dev.to/robertokerber/the-rise-of-geo-how-to-optimize-your-brand-for-ai-search-in-2026-1jbn</link>
      <guid>https://dev.to/robertokerber/the-rise-of-geo-how-to-optimize-your-brand-for-ai-search-in-2026-1jbn</guid>
      <description>&lt;p&gt;ChatGPT is the new Google. Gemini is the new search bar. Perplexity is rewriting how people find answers.&lt;/p&gt;

&lt;p&gt;If your brand isn't showing up in these AI assistants, you're invisible to the fastest-growing segment of internet users. This isn't future speculation — it's happening right now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generative Engine Optimization (GEO)&lt;/strong&gt; is the practice of optimizing your online presence so that AI engines surface your brand when users ask questions about your industry. And in 2026, it's the single most overlooked opportunity in digital marketing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Changed?
&lt;/h2&gt;

&lt;p&gt;Traditional SEO optimized for &lt;em&gt;links&lt;/em&gt;. You ranked pages based on backlinks, keywords, and domain authority. Google gave you 10 blue links, and the user clicked through.&lt;/p&gt;

&lt;p&gt;AI search doesn't work that way.&lt;/p&gt;

&lt;p&gt;When a user asks ChatGPT "which brand monitoring tool is best for small businesses", the model:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reads hundreds of sources&lt;/li&gt;
&lt;li&gt;Synthesizes the information&lt;/li&gt;
&lt;li&gt;Generates a single conversational answer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cites&lt;/strong&gt; the sources it used&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The point isn't to rank #1 on a SERP anymore. The point is to be &lt;strong&gt;cited&lt;/strong&gt; by the AI.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 3 Pillars of GEO
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Content That AI Understands
&lt;/h3&gt;

&lt;p&gt;AI models love structured, factual content. They prefer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clear headings and subheadings&lt;/li&gt;
&lt;li&gt;Bullet points and tables&lt;/li&gt;
&lt;li&gt;Specific data and statistics&lt;/li&gt;
&lt;li&gt;Authoritative citations&lt;/li&gt;
&lt;li&gt;FAQ sections with direct answers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Weak:&lt;/strong&gt; "Our product helps businesses monitor their online presence."&lt;br&gt;
&lt;strong&gt;Strong:&lt;/strong&gt; "According to our 2026 analysis, brands that monitor their AI visibility see a 340% increase in citation rate across ChatGPT, Gemini, and Perplexity."&lt;/p&gt;

&lt;p&gt;Specificity beats fluff every time with AI engines.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Authority Signals
&lt;/h3&gt;

&lt;p&gt;AI models don't just read your content — they evaluate your authority. Key signals include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Number and quality of external backlinks&lt;/li&gt;
&lt;li&gt;Consistency across multiple platforms (LinkedIn, Medium, Dev.to, your blog)&lt;/li&gt;
&lt;li&gt;Brand mentions in reputable publications&lt;/li&gt;
&lt;li&gt;Content freshness (regular updates, new articles)&lt;/li&gt;
&lt;li&gt;Structured data and schema markup&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Technical Accessibility
&lt;/h3&gt;

&lt;p&gt;If AI can't crawl it, you don't exist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast page load times (AI bots timeout just like users)&lt;/li&gt;
&lt;li&gt;Clean HTML that renders without JavaScript (some AI scrapers struggle with JS-heavy sites)&lt;/li&gt;
&lt;li&gt;Clear sitemaps that AI crawlers can navigate&lt;/li&gt;
&lt;li&gt;Robots.txt that doesn't block AI user agents&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The GEO Tracker Approach
&lt;/h2&gt;

&lt;p&gt;At AztecLab, we built the &lt;strong&gt;GEO Tracker&lt;/strong&gt; to solve exactly this problem. Instead of guessing which AI engines see your brand, we:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Scan ChatGPT, Gemini, Perplexity, Claude, and Google AI Overviews&lt;/li&gt;
&lt;li&gt;Collect every mention, citation, and representation of your brand&lt;/li&gt;
&lt;li&gt;Analyze sentiment, context, and visibility across all platforms&lt;/li&gt;
&lt;li&gt;Deliver a comprehensive PDF report in 24 hours&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We started with the problem every business faces: "Are we showing up in AI search?" The answer is rarely what companies expect.&lt;/p&gt;

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

&lt;p&gt;You don't need a massive budget or a dedicated team. Start with these three steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Audit your current AI visibility&lt;/strong&gt; — Search for your brand name and key products in ChatGPT and Gemini. What shows up?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix the gaps&lt;/strong&gt; — Create content that addresses the specific questions your customers ask. Publish consistently on platforms that AI models trust.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor continuously&lt;/strong&gt; — AI models change their sources regularly. Last month's citation can disappear next month.&lt;/li&gt;
&lt;/ol&gt;




&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Need to monitor your brand's AI presence?&lt;/strong&gt;&lt;br&gt;
The &lt;a href="https://geo.azteclab.cloud" rel="noopener noreferrer"&gt;GEO Tracker&lt;/a&gt; analyzes your company's visibility in ChatGPT, Gemini, Perplexity, Claude, and more — PDF report in 24h.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>seo</category>
      <category>marketing</category>
      <category>geo</category>
    </item>
    <item>
      <title>How to Check Your Brand's Visibility in ChatGPT, Gemini, and Perplexity (2026)</title>
      <dc:creator>Roberto Kerber</dc:creator>
      <pubDate>Sat, 29 Aug 2026 08:11:00 +0000</pubDate>
      <link>https://dev.to/robertokerber/how-to-check-your-brands-visibility-in-chatgpt-gemini-and-perplexity-2026-3ij3</link>
      <guid>https://dev.to/robertokerber/how-to-check-your-brands-visibility-in-chatgpt-gemini-and-perplexity-2026-3ij3</guid>
      <description>&lt;p&gt;If you have ever typed your company name into ChatGPT, Google Gemini, or Perplexity and wondered &lt;em&gt;"is this really how the world sees us?"&lt;/em&gt; — you already know the problem. AI search engines do not show ads, they do not offer a "search console" where you can submit your site, and they definitely do not tell you when or why your brand disappeared from their answers.&lt;/p&gt;

&lt;p&gt;This is what people in the industry are starting to call &lt;strong&gt;Generative Engine Optimization (GEO)&lt;/strong&gt; — a set of practices for understanding and improving how your brand shows up in AI-generated search results. Unlike traditional SEO, where Google's ranking factors are reasonably well understood, each AI model is a black box fed by a different mix of training data, real-time search results, and internal ranking signals.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "visibility" means in the AI era
&lt;/h2&gt;

&lt;p&gt;With Google, you had rankings: position 1 through position 100. You could measure it, graph it, and optimize for it. With AI search, the concept of "visibility" is fundamentally different in three ways:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attribution is sparse.&lt;/strong&gt; When ChatGPT lists five product recommendations, only one brand gets named. The other four might as well not exist. There is no "page 2" where you can still be found.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context matters more than keywords.&lt;/strong&gt; The same AI model may recommend your business when asked "find me a plumber in Lisbon" but ignore you completely when asked "emergency plumbing services Portugal" — even though the real-world intent is identical. Understanding which phrasing triggers a mention is the core of GEO.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hallucination is a real risk.&lt;/strong&gt; Some businesses discover that AI models have fabricated negative information about them — a competitor's review attributed to them, a service they never offered listed as a specialty, or worse, an incorrect address or phone number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why manual checking does not scale
&lt;/h2&gt;

&lt;p&gt;You could open each AI tool and type queries manually. For one brand in one language on one model, that takes about 15 minutes. Now multiply by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;6+ AI search engines (ChatGPT, Gemini, Perplexity, Claude, Copilot, Grok)&lt;/li&gt;
&lt;li&gt;Multiple query variants per engine&lt;/li&gt;
&lt;li&gt;Multiple languages (pt-PT, pt-BR, EN, ES)&lt;/li&gt;
&lt;li&gt;Regular monitoring (weekly, not once)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The math lands at 20+ hours per week for a single brand. That is where a GEO tracking tool like &lt;a href="https://geo.azteclab.cloud" rel="noopener noreferrer"&gt;GEO Tracker&lt;/a&gt; comes in — it automates the query execution across all major AI models and compiles the result into a report you can actually act on.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the report should contain
&lt;/h2&gt;

&lt;p&gt;If you decide to measure your AI visibility, here is the minimum set of data points worth collecting each week:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For each AI model:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did our brand appear? (yes/no/partial)&lt;/li&gt;
&lt;li&gt;In what context? (recommendation, mention, comparison, list)&lt;/li&gt;
&lt;li&gt;Positive, neutral, or negative framing?&lt;/li&gt;
&lt;li&gt;Was any incorrect information attributed to us?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Across all models:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which queries produce the highest mention rate?&lt;/li&gt;
&lt;li&gt;Which competitor appears most often across models?&lt;/li&gt;
&lt;li&gt;Which language has the best coverage?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What to do with the data
&lt;/h2&gt;

&lt;p&gt;Once you have a baseline, the optimization loop is surprisingly similar to old-school SEO:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fill gaps&lt;/strong&gt; — if a model never mentions you, check whether your structured data (Organization + LocalBusiness schema) is correct and crawlable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reinforce positive signals&lt;/strong&gt; — when a model already recommends you in one language, publish equivalent content in your other target languages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Correct hallucinations&lt;/strong&gt; — if a model attributes wrong info to your brand, the fix usually requires updating authoritative sources (your website, Wikipedia, Crunchbase) and waiting for the next training cycle&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Track competitors&lt;/strong&gt; — when a competitor appears where you don't, analyze what signals they have that you lack&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Traditional SEO is not dead, but it is no longer the only game in town. Businesses that ignore their AI search presence today will find themselves invisible to an entire generation of users who never click "View all" — they just take the AI's answer and move on.&lt;/p&gt;

</description>
      <category>seo</category>
      <category>generativeai</category>
      <category>webscraping</category>
      <category>marketing</category>
    </item>
    <item>
      <title>How to Scrape Google Play for ASO, Keyword &amp; App Data (2026)</title>
      <dc:creator>Roberto Kerber</dc:creator>
      <pubDate>Sat, 29 Aug 2026 08:04:48 +0000</pubDate>
      <link>https://dev.to/robertokerber/how-to-scrape-google-play-for-aso-keyword-app-data-2026-in2</link>
      <guid>https://dev.to/robertokerber/how-to-scrape-google-play-for-aso-keyword-app-data-2026-in2</guid>
      <description>&lt;p&gt;If you are trying to &lt;strong&gt;scrape Google Play&lt;/strong&gt; for Android ASO research, keyword tracking, or competitor app data, you already know it is not the same problem as the iOS side: Google does not publish anything close to Apple's iTunes Search API. There is a Play Console Developer API, but it only serves data for apps you own - it will not let you look up a competitor's rating or search for who ranks on a keyword. This post walks through what scraping Google Play actually involves without an official API, and how to get clean structured app data without reverse-engineering a page yourself. Whether you searched "google play scraper", "android aso" or "google play api" to get here, the tradeoffs below apply either way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why scraping Google Play is harder than it looks
&lt;/h2&gt;

&lt;p&gt;There is no public, documented API for reading someone else's app data on Google Play. Everything that library authors and scrapers rely on comes from the Play Store's own web page: when you load &lt;code&gt;play.google.com/store/apps/details?id=...&lt;/code&gt; in a browser, the rating, install count, and description are not returned as clean JSON from an endpoint - they are embedded inside a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag as a deeply nested array, addressed by numeric position rather than named fields (&lt;code&gt;AF_initDataCallback&lt;/code&gt; blobs, in the terminology scraping libraries use for this pattern). There is no schema, no versioning, and no changelog when Google alters it.&lt;/p&gt;

&lt;p&gt;That has a few concrete consequences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Positional, not named, fields.&lt;/strong&gt; You are not reading &lt;code&gt;app["rating"]&lt;/code&gt; - you are reading &lt;code&gt;data[1][2][51][0]&lt;/code&gt; and hoping that index still means "rating" after the next Play Store frontend deploy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Silent breakage.&lt;/strong&gt; When the blob structure shifts, you don't get an error - you get &lt;code&gt;None&lt;/code&gt; or the wrong value in a field that used to work, and nothing tells you which index moved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search has no clean equivalent either.&lt;/strong&gt; The Play Store search results page renders similarly - keyword ranking has to be scraped from the same kind of embedded blob, in results order, with no separate "ranking API" to call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No official rate limit guidance, because there's no official API.&lt;/strong&gt; You are making requests against a consumer-facing web page, not a developer product, so there is no documented ceiling - only the point where you start getting blocked or served degraded HTML.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Approach 1: DIY with Python
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Attempt 1: plain requests against the app detail page
&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;requests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_playstore_app_raw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;package_name&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="n"&gt;country&lt;/span&gt;&lt;span class="o"&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="n"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;en&lt;/span&gt;&lt;span class="sh"&gt;"&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://play.google.com/store/apps/details?id=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;package_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;gl=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;hl=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;lang&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;r&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;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# the app's data lives inside a script tag as a JS array literal,
&lt;/span&gt;    &lt;span class="c1"&gt;# not as a JSON endpoint response
&lt;/span&gt;    &lt;span class="n"&gt;match&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;search&lt;/span&gt;&lt;span class="p"&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;AF_initDataCallback\(({.*?})\);&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DOTALL&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;match&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&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;# still needs JS-literal parsing, not just json.loads
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gets you the HTML, but the payload is a JavaScript object literal embedded in a script tag, not valid JSON on its own - you need a parser tolerant of JS syntax, and then you need to know which numeric index in that nested array holds &lt;code&gt;rating&lt;/code&gt;, which holds &lt;code&gt;installs&lt;/code&gt;, and so on. That mapping is not documented anywhere; it is discovered by diffing known values against the blob and is exactly the part that breaks on a frontend redesign.&lt;/p&gt;

&lt;h3&gt;
  
  
  Attempt 2: search mode (same problem, ranked results)
&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;search_playstore_raw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;term&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="n"&gt;country&lt;/span&gt;&lt;span class="o"&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="n"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;en&lt;/span&gt;&lt;span class="sh"&gt;"&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://play.google.com/store/search?q=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;c=apps&amp;amp;gl=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;hl=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;lang&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;r&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;timeout&lt;/span&gt;&lt;span class="o"&gt;=&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;match&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;search&lt;/span&gt;&lt;span class="p"&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;AF_initDataCallback\(({.*?})\);&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DOTALL&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;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="c1"&gt;# results are embedded in the same positional-array format,
&lt;/span&gt;    &lt;span class="c1"&gt;# in the order the Play Store ranks them for the keyword
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same wall, applied to search: the ranking order is real and useful, but extracting &lt;code&gt;appId&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;developer&lt;/code&gt; per result means navigating the same undocumented nested structure, and there is no separate "give me just the ranking" endpoint to fall back on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real cost here is the reverse-engineering, not the request itself
&lt;/h2&gt;

&lt;p&gt;A single lookup you can eyeball and fix by hand is fine to hand-roll once. The cost shows up when the blob structure shifts and every app in your pipeline returns wrong or missing fields at the same time, with no error telling you why.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;DIY (reverse-engineered blob parsing)&lt;/th&gt;
&lt;th&gt;Managed scraper (API)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Upfront cost&lt;/td&gt;
&lt;td&gt;Free (your time)&lt;/td&gt;
&lt;td&gt;Pay per result returned&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Official API&lt;/td&gt;
&lt;td&gt;None exists for third-party app data&lt;/td&gt;
&lt;td&gt;N/A - handled regardless&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Field mapping&lt;/td&gt;
&lt;td&gt;You maintain a positional index map&lt;/td&gt;
&lt;td&gt;Comes back as named JSON fields&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Breakage on redesign&lt;/td&gt;
&lt;td&gt;Silent - wrong values, no error&lt;/td&gt;
&lt;td&gt;Maintained on the provider's side&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search ranking&lt;/td&gt;
&lt;td&gt;Same blob format, no separate endpoint&lt;/td&gt;
&lt;td&gt;Same schema as lookup mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-platform (+iOS)&lt;/td&gt;
&lt;td&gt;A second, unrelated scraper to write&lt;/td&gt;
&lt;td&gt;Pairs with an App Store ASO actor on the same model&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Neither column is objectively "right." A one-off check on a single app is genuinely fine to hand-parse once, and the snippets above will get you the raw blob. Recurring monitoring across a competitor set is where a silent index shift becomes an expensive surprise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach 2: a ready-made Google Play scraper (API)
&lt;/h2&gt;

&lt;p&gt;This is the part where I show you the shortcut. &lt;a href="https://apify.com/plum_spear/aztec-googleplay-aso" rel="noopener noreferrer"&gt;&lt;strong&gt;Google Play Scraper&lt;/strong&gt;&lt;/a&gt; is an Apify actor that does the blob parsing and index mapping for you and returns named, typed JSON fields - no reverse engineering, no proxy, no login.&lt;/p&gt;

&lt;h3&gt;
  
  
  Input
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;search&lt;/code&gt; (apps by keyword) or &lt;code&gt;lookup&lt;/code&gt; (apps by package name)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;search&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;term&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Keyword to search for (search mode)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fitness tracker&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;appIds&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;array&lt;/td&gt;
&lt;td&gt;Android package names to look up (lookup mode)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;["com.whatsapp", "com.spotify.music"]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;country&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Two-letter country code for the storefront&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;us&lt;/code&gt;, &lt;code&gt;br&lt;/code&gt;, &lt;code&gt;de&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;language&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Two-letter language code&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;en&lt;/code&gt;, &lt;code&gt;pt&lt;/code&gt;, &lt;code&gt;de&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;maxResults&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;td&gt;Max apps to return in search mode (up to 30)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;30&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lookup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"appIds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"com.whatsapp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.spotify.music"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;A real lookup result for WhatsApp Messenger (&lt;code&gt;com.whatsapp&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"appId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.whatsapp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WhatsApp Messenger"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4.66&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ratings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;237058214&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reviews"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1962977&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"installs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"10,000,000,000+"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"minInstalls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10000000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"free"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"genre"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Communication"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"developer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WhatsApp LLC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"released"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Oct 18, 2010"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"updated"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Jun 10, 2026"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Varies with device"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"contentRating"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Everyone"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"adSupported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Simple. Reliable. Private. Message and call for free."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"descriptionPreview"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WhatsApp from Meta is a free messaging and video calling app..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"icon"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://play-lh.googleusercontent.com/.../icon.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://play.google.com/store/apps/details?id=com.whatsapp"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;minInstalls&lt;/code&gt; comes back as an integer you can sort and filter on directly, instead of parsing &lt;code&gt;"10,000,000,000+"&lt;/code&gt; yourself. Search mode returns a lighter subset of these fields, in Play Store ranking order.&lt;/p&gt;

&lt;h3&gt;
  
  
  Calling it from JavaScript
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ApifyClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apify-client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;YOUR_APIFY_TOKEN&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;plum_spear/aztec-googleplay-aso&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;search&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;term&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fitness tracker&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;us&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;maxResults&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;listItems&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apps&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Calling it from Python
&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;from&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ApifyClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;YOUR_APIFY_TOKEN&amp;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;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plum_spear/aztec-googleplay-aso&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_input&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;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;lookup&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;appIds&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;com.whatsapp&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;com.spotify.music&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;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;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&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;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&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;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="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;score&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;minInstalls&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;
  
  
  Calling it from the CLI or plain REST
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apify call plum_spear/aztec-googleplay-aso &lt;span class="nt"&gt;--input&lt;/span&gt; &lt;span class="s1"&gt;'{"mode": "search", "term": "fitness tracker", "country": "us", "maxResults": 30}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://api.apify.com/v2/acts/plum_spear~aztec-googleplay-aso/run-sync-get-dataset-items?token=&amp;lt;YOUR_APIFY_TOKEN&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"mode": "lookup", "appIds": ["com.whatsapp"], "country": "us"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What people actually build with this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ASO managers&lt;/strong&gt; run search mode against target keywords to see who currently ranks, then switch to lookup mode to track ratings and install counts of specific competitors over time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;App marketers&lt;/strong&gt; quantify a category before spending on user acquisition - who dominates a keyword, how big their install base is, and whether the niche skews free or paid.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Market researchers&lt;/strong&gt; measure category size, pricing distribution, and developer concentration across hundreds of apps for reports and trend analysis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Indie Android developers&lt;/strong&gt; validate an app idea against real data - keyword saturation, competitor pricing, install base - before writing a line of code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-platform teams&lt;/strong&gt; pair this with an App Store ASO scraper to build one consistent iOS + Android dataset instead of maintaining two unrelated pipelines.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;p&gt;Pay-per-event: &lt;strong&gt;$0.15 per 1,000 results returned&lt;/strong&gt;, plus a minimal actor-start event. No subscription, no monthly minimum - you only pay for the app records you actually get. Apify's free monthly platform credits are enough to test a real query before deciding whether it's worth it.&lt;/p&gt;

&lt;p&gt;For context: the DIY route above means maintaining a positional index map into an undocumented blob that can shift without notice on any Play Store frontend update. $0.15 per 1,000 named, typed results is a small price for not carrying that maintenance risk yourself, especially once you're tracking more than one or two apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using it from an AI agent (MCP)
&lt;/h2&gt;

&lt;p&gt;If you're wiring this into an agent instead of a script, actors published on Apify, including this one, are reachable through &lt;a href="https://mcp.apify.com" rel="noopener noreferrer"&gt;Apify's MCP server&lt;/a&gt;, which exposes them as callable tools for MCP-compatible clients. Same &lt;code&gt;mode&lt;/code&gt; / &lt;code&gt;term&lt;/code&gt; / &lt;code&gt;appIds&lt;/code&gt; input, no separate integration to write.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;Scraping a single Google Play app yourself is doable - the snippets above will get you the raw blob, and for a one-off check that's enough. What they won't do on their own is survive the next frontend redesign, or hand you named, typed fields you can pipe straight into a spreadsheet or dashboard without maintaining a positional index map by hand. That gap is what &lt;a href="https://apify.com/plum_spear/aztec-googleplay-aso" rel="noopener noreferrer"&gt;Google Play Scraper&lt;/a&gt; on Apify closes: pick a &lt;code&gt;mode&lt;/code&gt;, get back clean JSON with rating, installs, price and developer already parsed, priced at $0.15 per 1,000 results with no monthly commitment.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Precisa monitorizar a sua marca nestas plataformas?&lt;/strong&gt;&lt;br&gt;
O &lt;a href="https://geo.azteclab.cloud" rel="noopener noreferrer"&gt;GEO Tracker&lt;/a&gt; analisa a presença da sua empresa no ChatGPT, Gemini, Perplexity, Claude e mais — relatório PDF em 24h.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>webscraping</category>
      <category>python</category>
      <category>javascript</category>
      <category>api</category>
    </item>
    <item>
      <title>How to Scrape the Apple App Store for ASO &amp; Keyword Research (2026)</title>
      <dc:creator>Roberto Kerber</dc:creator>
      <pubDate>Fri, 28 Aug 2026 12:06:00 +0000</pubDate>
      <link>https://dev.to/robertokerber/how-to-scrape-the-apple-app-store-for-aso-keyword-research-2026-327o</link>
      <guid>https://dev.to/robertokerber/how-to-scrape-the-apple-app-store-for-aso-keyword-research-2026-327o</guid>
      <description>&lt;p&gt;If you are trying to &lt;strong&gt;scrape the Apple App Store&lt;/strong&gt; for ASO research, keyword tracking, or competitor monitoring, here is the honest version most posts skip: this one is genuinely easier than most scraping targets, because Apple exposes a public search API and public chart feeds that need no login and no proxy. The real cost isn't getting the data out - it's normalizing three different response shapes (search, lookup, charts) into one schema, handling Apple's undocumented rate limits, and building the scheduling and history layer on top so a snapshot becomes a trend. This post covers both. Whether you searched "app store scraper", "aso tool" or "app store keyword api" to get here, the tradeoffs below apply either way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is easier than most scrapers, and where it still bites you
&lt;/h2&gt;

&lt;p&gt;Apple's &lt;strong&gt;iTunes Search API&lt;/strong&gt; (&lt;code&gt;itunes.apple.com/search&lt;/code&gt;) and the App Store's public RSS chart feeds are unauthenticated, documented (loosely), and return JSON directly - no headless browser, no residential proxy, no client-side rendering to fight. For search-by-keyword and lookup-by-ID, a plain &lt;code&gt;requests.get()&lt;/code&gt; gets you real data on the first try. That's the good news, and it's worth saying plainly instead of manufacturing a horror story that isn't there.&lt;/p&gt;

&lt;p&gt;Where it still costs you real engineering time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Undocumented rate limits.&lt;/strong&gt; Apple does not publish a request-per-minute ceiling for the Search API. Hit it too hard and you start getting empty results or throttled responses with no clear error explaining why - you find the limit by tripping it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three different response shapes.&lt;/strong&gt; Search and lookup return the same iTunes JSON schema, but the RSS chart feeds (&lt;code&gt;topfreeapplications&lt;/code&gt;, &lt;code&gt;topgrossingapplications&lt;/code&gt;, etc.) come back in a different structure entirely, so "one App Store scraper" is really three code paths you have to keep in sync.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No native ranking history.&lt;/strong&gt; Every response is a snapshot. Trend detection (a rating that moved, a competitor climbing the charts) only exists if you build the storage and diffing layer yourself - the API gives you no memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Country and entity matrix.&lt;/strong&gt; Multiply keywords by storefronts (&lt;code&gt;us&lt;/code&gt;, &lt;code&gt;gb&lt;/code&gt;, &lt;code&gt;br&lt;/code&gt;, &lt;code&gt;de&lt;/code&gt;, ...) and entity types (&lt;code&gt;software&lt;/code&gt;, &lt;code&gt;iPadSoftware&lt;/code&gt;, &lt;code&gt;macSoftware&lt;/code&gt;, ...) and you are running and merging a lot of small requests, not one big one.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Approach 1: DIY with Python
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Attempt 1: keyword search and ID lookup (this 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;requests&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_apps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;term&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="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&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="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&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;r&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://itunes.apple.com/search&lt;/span&gt;&lt;span class="sh"&gt;"&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;term&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;term&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="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;entity&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;software&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;limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;results&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;lookup_apps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&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="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&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="n"&gt;r&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://itunes.apple.com/lookup&lt;/span&gt;&lt;span class="sh"&gt;"&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;id&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;,&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;app_ids&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="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;results&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;No proxy, no browser, no anti-bot fight. Run this and you get real ratings, prices and metadata back in one request. The catch shows up once you scale it: run this in a tight loop across many keywords and countries and you start hitting undocumented throttling, with nothing in the response body explaining that you've been rate-limited versus that zero apps genuinely matched.&lt;/p&gt;

&lt;h3&gt;
  
  
  Attempt 2: Top Charts (a different shape entirely)
&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;get_top_charts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;topfreeapplications&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&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="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&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://itunes.apple.com/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/rss/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;feed&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/limit=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;r&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;timeout&lt;/span&gt;&lt;span class="o"&gt;=&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;entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;feed&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;entry&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="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;rank&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;i&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="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="n"&gt;e&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;attributes&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;im:id&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;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;e&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;im: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;label&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;developer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;im:artist&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;label&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;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entries&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 is a completely different response shape from search/lookup - &lt;code&gt;im:&lt;/code&gt;-prefixed field names, a different nesting level, no rating or price data at all (charts feeds give you ranking, not metadata; you have to &lt;code&gt;lookup_apps()&lt;/code&gt; afterward if you want ratings for the ranked IDs). Two API surfaces, one mental model you have to build to keep them consistent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real cost here isn't fetching data, it's normalizing and remembering it
&lt;/h2&gt;

&lt;p&gt;Because the raw fetch is genuinely free and reliable, DIY is a fair choice for a single check. The cost shows up when you need one consistent schema across modes, historical trend data, and scheduled runs.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;DIY (Python, direct API calls)&lt;/th&gt;
&lt;th&gt;Managed scraper (API)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Upfront cost&lt;/td&gt;
&lt;td&gt;Free (your time)&lt;/td&gt;
&lt;td&gt;Pay per result returned&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fetching data&lt;/td&gt;
&lt;td&gt;Genuinely easy - public JSON endpoints&lt;/td&gt;
&lt;td&gt;Same endpoints, wrapped for you&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rate limit handling&lt;/td&gt;
&lt;td&gt;You find the ceiling by tripping it&lt;/td&gt;
&lt;td&gt;Handled on the provider's side&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search / lookup / charts schema&lt;/td&gt;
&lt;td&gt;Three shapes you normalize yourself&lt;/td&gt;
&lt;td&gt;One consistent output schema&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Country x entity matrix&lt;/td&gt;
&lt;td&gt;You loop and merge manually&lt;/td&gt;
&lt;td&gt;One input, same schema every time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;History / scheduling&lt;/td&gt;
&lt;td&gt;You build storage + diffing&lt;/td&gt;
&lt;td&gt;Native scheduler, append to a dataset&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Neither column is objectively "right." A one-off keyword check is genuinely fine to write yourself with the snippets above - that's the point of showing they work. Recurring, cross-country, cross-mode monitoring is where the normalization and scheduling work adds up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach 2: a ready-made App Store ASO scraper (API)
&lt;/h2&gt;

&lt;p&gt;This is the part where I show you the shortcut. &lt;a href="https://apify.com/plum_spear/aztec-appstore-aso" rel="noopener noreferrer"&gt;&lt;strong&gt;App Store Scraper &amp;amp; ASO Tool&lt;/strong&gt;&lt;/a&gt; is an Apify actor that wraps search, lookup and charts behind one input and one output schema, with rate limiting, country handling and dataset history already built in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Input
&lt;/h3&gt;

&lt;p&gt;Only the fields relevant to your chosen &lt;code&gt;mode&lt;/code&gt; are required.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;search&lt;/code&gt;, &lt;code&gt;lookup&lt;/code&gt;, or &lt;code&gt;charts&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;search&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;term&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Keyword to search for (search mode)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fitness tracker&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;appIds&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;One or more App Store IDs (lookup mode)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;["310633997"]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;feed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Chart to fetch (charts mode)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;topfreeapplications&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;entity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;App type (search mode)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;software&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;country&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Two-letter storefront code&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;us&lt;/code&gt;, &lt;code&gt;gb&lt;/code&gt;, &lt;code&gt;br&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;maxResults&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Maximum results to return (up to 200)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;50&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"search"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"term"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fitness tracker"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"entity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"software"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxResults"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"charts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"feed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"topfreeapplications"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gb"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxResults"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;The same shape whether the record came from search, lookup, or charts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"appId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;389801252&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Instagram"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"developer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Instagram, Inc."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rating"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ratingCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;28000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"genre"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Photo &amp;amp; Video"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"350.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"releaseDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2010-10-06T19:12:14Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"updatedDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-06-10T08:31:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"contentRating"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"12+"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://apps.apple.com/us/app/id389801252"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"icon"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://is1-ssl.mzstatic.com/image/.../512x512bb.jpg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Little moments lead to big friendships..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In charts mode, each record also includes a &lt;code&gt;rank&lt;/code&gt; field - so you get ranking and metadata in the same response, no separate lookup call needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Calling it from JavaScript
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ApifyClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apify-client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;YOUR_APIFY_TOKEN&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;plum_spear/aztec-appstore-aso&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;search&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;term&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fitness tracker&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;us&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;maxResults&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="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;listItems&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apps&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Calling it from Python
&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;from&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ApifyClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;YOUR_APIFY_TOKEN&amp;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;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plum_spear/aztec-appstore-aso&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_input&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;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;lookup&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;appIds&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;389801252&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;310633997&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;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="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&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;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;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;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;rating&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;version&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;
  
  
  Calling it from the CLI or plain REST
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apify call plum_spear/aztec-appstore-aso &lt;span class="nt"&gt;--input&lt;/span&gt; &lt;span class="s1"&gt;'{"mode": "charts", "feed": "topfreeapplications", "country": "us", "maxResults": 100}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://api.apify.com/v2/acts/plum_spear~aztec-appstore-aso/run-sync-get-dataset-items?token=&amp;lt;YOUR_APIFY_TOKEN&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"mode": "search", "term": "fitness tracker", "country": "us", "maxResults": 50}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What people actually build with this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ASO managers&lt;/strong&gt; run search mode against target keywords to see who owns them, then lookup mode on a fixed competitor list to track rating, version and update-date shifts week over week.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;App marketers&lt;/strong&gt; monitor Top Charts daily to catch a rising competitor before it breaks into the top ranks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Market researchers&lt;/strong&gt; quantify an entire category in one run - who ranks, how they price, how well they're rated - for sizing studies or reports.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Indie developers&lt;/strong&gt; validate an app idea by checking how saturated a keyword already is and what the top apps charge, before writing a line of code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Growth and data teams&lt;/strong&gt; use it as a building block: scheduled runs feed a data warehouse or alerting pipeline for continuous market monitoring.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;p&gt;Pay-per-event: &lt;strong&gt;$0.15 per 1,000 results returned&lt;/strong&gt;, plus a minimal actor-start event. No subscription, no monthly minimum, no hidden proxy costs - you only pay for the app records you actually get. Apify's free monthly platform credits are enough to try a real query before deciding whether it's worth it.&lt;/p&gt;

&lt;p&gt;For context: the raw API calls above are free, so a single keyword check is genuinely fine to do yourself. Once you need one schema across search, lookup and charts, plus scheduled history so a snapshot becomes a trend, $0.15 per 1,000 results is a small price for not building and maintaining that normalization layer yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using it from an AI agent (MCP)
&lt;/h2&gt;

&lt;p&gt;If you're wiring this into an agent instead of a script, actors published on Apify, including this one, are reachable through &lt;a href="https://mcp.apify.com" rel="noopener noreferrer"&gt;Apify's MCP server&lt;/a&gt;, which exposes them as callable tools for MCP-compatible clients. Same &lt;code&gt;mode&lt;/code&gt; / &lt;code&gt;term&lt;/code&gt; / &lt;code&gt;appIds&lt;/code&gt; / &lt;code&gt;feed&lt;/code&gt; input, no separate integration to write.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;Pulling raw App Store data yourself is genuinely easy - no proxy, no browser, just public JSON endpoints, and the snippets above will get you there for a one-off check. What they won't do on their own is give you one consistent schema across search, lookup and charts, handle Apple's undocumented rate limits gracefully, or turn a snapshot into a trend over time. That gap is what &lt;a href="https://apify.com/plum_spear/aztec-appstore-aso" rel="noopener noreferrer"&gt;App Store Scraper &amp;amp; ASO Tool&lt;/a&gt; on Apify closes: pick a &lt;code&gt;mode&lt;/code&gt;, get back the same clean JSON schema every time, priced at $0.15 per 1,000 results with no monthly commitment.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Precisa monitorizar a sua marca nos assistentes de IA?&lt;/strong&gt;&lt;br&gt;
O &lt;a href="https://geo.azteclab.cloud" rel="noopener noreferrer"&gt;GEO Tracker&lt;/a&gt; analisa a presença da sua empresa no ChatGPT, Gemini, Perplexity, Claude e mais — relatório PDF em 24h.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>webscraping</category>
      <category>python</category>
      <category>javascript</category>
      <category>api</category>
    </item>
    <item>
      <title>How to Scrape OLX Europe: Classified Ads &amp; Prices Across Markets (2026)</title>
      <dc:creator>Roberto Kerber</dc:creator>
      <pubDate>Fri, 28 Aug 2026 09:20:30 +0000</pubDate>
      <link>https://dev.to/robertokerber/how-to-scrape-olx-europe-classified-ads-prices-across-markets-2026-4g5g</link>
      <guid>https://dev.to/robertokerber/how-to-scrape-olx-europe-classified-ads-prices-across-markets-2026-4g5g</guid>
      <description>&lt;p&gt;If you are trying to &lt;strong&gt;scrape OLX&lt;/strong&gt; across several European markets - Portugal, Poland, Romania, Bulgaria, Ukraine, Kazakhstan - to monitor prices, generate leads, or compare a category across countries, you already know the problem multiplies with every market you add: it is not one scraper you have to maintain, it is potentially six. This post walks through what a DIY multi-country OLX scraper actually costs you, and how to get clean structured data across all six markets from a single input without maintaining six scrapers. Whether you searched "olx scraper europe" or "olx api" to get here, the tradeoffs below apply either way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why scraping OLX across multiple countries is harder than it looks
&lt;/h2&gt;

&lt;p&gt;Each OLX country storefront runs on the same underlying platform (the CDN paths give it away - thumbnails come from &lt;code&gt;apollo.olxcdn.com&lt;/code&gt; across markets), which means a scraper built for one country transfers conceptually to the others. In practice that "transfer" is exactly where the work hides.&lt;/p&gt;

&lt;p&gt;The listing grid renders client-side, so a plain &lt;code&gt;requests.get()&lt;/code&gt; returns an app shell with no ad data in it, in every country - you need a real browser or a way to call the same internal API the page itself calls. Anti-bot protection is applied per market: datacenter IPs get rate-limited or blocked, and you need a residential IP that can actually reach the country storefront you're targeting, not just any residential IP.&lt;/p&gt;

&lt;p&gt;Then the parts that look small until you multiply them by six:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Currency and formatting&lt;/strong&gt; - price shows as &lt;code&gt;"450 €"&lt;/code&gt; in Portugal, differently formatted in Polish złoty or Romanian leu; you parse each format or write one parser robust enough for all of them&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Category IDs&lt;/strong&gt; - OLX category taxonomy is not identical across countries, so a &lt;code&gt;categoryId&lt;/code&gt; that means "phones" in one market does not necessarily mean the same thing in another&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Language&lt;/strong&gt; - titles, descriptions and category names come back in the local language, which affects any keyword-based filtering you do downstream&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Layout drift, per country&lt;/strong&gt; - a redesign can ship to one storefront before the others, so "it broke" can mean one-sixth of your pipeline silently going empty while the rest keeps working&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is hard for a single country. Maintaining it correctly across six, with someone noticing the moment one of them silently breaks, is the actual cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach 1: DIY with Python
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Attempt 1: plain requests (does not work)
&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;requests&lt;/span&gt;

&lt;span class="n"&gt;r&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.olx.pt/ads/q-iphone/&lt;/span&gt;&lt;span class="sh"&gt;"&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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;# 200, but the ad grid is empty in the raw HTML - same story
# on every OLX country storefront, not just Portugal.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Attempt 2: one Playwright scraper, looped over countries
&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;from&lt;/span&gt; &lt;span class="n"&gt;playwright.sync_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sync_playwright&lt;/span&gt;

&lt;span class="n"&gt;OLX_DOMAINS&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;pt&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;olx.pt&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;pl&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;olx.pl&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;ro&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;olx.ro&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;bg&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;olx.bg&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;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;olx.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;kz&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;olx.kz&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;def&lt;/span&gt; &lt;span class="nf"&gt;scrape_olx_country&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="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_ads&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;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OLX_DOMAINS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;listings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;sync_playwright&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;headless&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;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new_page&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&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;https://www.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/ads/q-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;query&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;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait_for_selector&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-cy=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;l-card&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;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;15000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;cards&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query_selector_all&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-cy=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;l-card&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;card&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cards&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;max_ads&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query_selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;h6&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;price_raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query_selector&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-testid=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ad-price&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;listings&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;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inner_text&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;title&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&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_raw&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;price_raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inner_text&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_raw&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&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="n"&gt;country&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="n"&gt;browser&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;return&lt;/span&gt; &lt;span class="n"&gt;listings&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_price&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price_raw&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# "450 €" -&amp;gt; 450.0 - written for one currency format,
&lt;/span&gt;    &lt;span class="c1"&gt;# needs testing against every market's actual formatting
&lt;/span&gt;    &lt;span class="n"&gt;digits&lt;/span&gt; &lt;span class="o"&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;c&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;price_raw&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&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="n"&gt;c&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="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;digits&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;,&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;.&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="n"&gt;digits&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Selector names above are illustrative. This works for one market at a time, on your own IP, for a small run. Scale it to six countries on a schedule and you now own six IP-blocking surfaces, six selector-drift risks, and a price parser that has to be correct for six currency formats - not one problem, six copies of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real cost of DIY isn't one scraper, it's six of them staying in sync
&lt;/h2&gt;

&lt;p&gt;Pulling a handful of listings from one country once is genuinely fine to hand-roll. The cost shows up when you need consistent, structured coverage across markets, on a schedule.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;DIY (Playwright + your own infra, x6 countries)&lt;/th&gt;
&lt;th&gt;Managed scraper (API)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Upfront cost&lt;/td&gt;
&lt;td&gt;Free (your time, x6 markets)&lt;/td&gt;
&lt;td&gt;Pay per ad returned&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JS rendering&lt;/td&gt;
&lt;td&gt;You maintain a headless browser per market&lt;/td&gt;
&lt;td&gt;Handled for you&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IP blocking&lt;/td&gt;
&lt;td&gt;Residential proxies routed per country&lt;/td&gt;
&lt;td&gt;Already routed through a residential IP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Currency / price parsing&lt;/td&gt;
&lt;td&gt;You write and test six formats&lt;/td&gt;
&lt;td&gt;Comes back as one typed &lt;code&gt;price&lt;/code&gt; field&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Selector breakage&lt;/td&gt;
&lt;td&gt;You detect and fix it per market&lt;/td&gt;
&lt;td&gt;Maintained on the provider's side&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-country comparison&lt;/td&gt;
&lt;td&gt;You normalize schemas yourself&lt;/td&gt;
&lt;td&gt;Same schema everywhere, &lt;code&gt;country&lt;/code&gt; is one field&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Neither column is objectively "right." If you need a one-off pull from a single country, the Playwright script above is enough. If you need consistent coverage across several of the six markets, on a schedule, the multiplication is the real cost, not the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach 2: a ready-made OLX Europe scraper (API)
&lt;/h2&gt;

&lt;p&gt;This is the part where I show you the shortcut. &lt;a href="https://apify.com/plum_spear/aztec-olx-eu" rel="noopener noreferrer"&gt;&lt;strong&gt;OLX Europe Scraper&lt;/strong&gt;&lt;/a&gt; is an Apify actor that covers all six markets - Portugal, Poland, Romania, Bulgaria, Ukraine, Kazakhstan - from a single input, over a residential IP, with prices already parsed and a consistent schema across countries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Input
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;query&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Search keyword to look up on OLX&lt;/td&gt;
&lt;td&gt;&lt;code&gt;iphone&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;country&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;pt&lt;/code&gt;, &lt;code&gt;pl&lt;/code&gt;, &lt;code&gt;ro&lt;/code&gt;, &lt;code&gt;bg&lt;/code&gt;, &lt;code&gt;ua&lt;/code&gt;, or &lt;code&gt;kz&lt;/code&gt; (default &lt;code&gt;pt&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pt&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;categoryId&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Optional OLX category ID to narrow the search&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1953&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;maxAds&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;number&lt;/td&gt;
&lt;td&gt;Maximum number of ads to scrape (default &lt;code&gt;100&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;100&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"iphone"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxAds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change only &lt;code&gt;country&lt;/code&gt; to run the identical query against a different market - no separate config, no separate parser.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1093847562&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"iPhone 13 128GB Azul - Como Novo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;450&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"priceLabel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"450 €"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"iPhone 13 em excelente estado, 128GB, com caixa e carregador. Bateria a 92%."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"city"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Lisboa"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Lisboa"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-06-19T10:14:00+01:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"refreshed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-06-23T08:02:00+01:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"business"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Telemóveis"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"imageCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"thumbnail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://apollo.olxcdn.com/v1/files/..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.olx.pt/d/anuncio/..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;price&lt;/code&gt; is a parsed number in every country's response, &lt;code&gt;priceLabel&lt;/code&gt; keeps the original string for display, and &lt;code&gt;business&lt;/code&gt; tells professional sellers apart from private ones - the same three fields, same meaning, regardless of which of the six markets the ad came from.&lt;/p&gt;

&lt;h3&gt;
  
  
  Calling it from JavaScript
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ApifyClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apify-client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;YOUR_APIFY_TOKEN&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;plum_spear/aztec-olx-eu&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;iphone&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;maxAds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;listItems&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ads&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Calling it from Python
&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;from&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ApifyClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;YOUR_APIFY_TOKEN&amp;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;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plum_spear/aztec-olx-eu&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_input&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;iphone&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;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;pt&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;maxAds&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&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;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&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;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="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="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;city&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;
  
  
  Calling it from the CLI or plain REST
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apify call plum_spear/aztec-olx-eu &lt;span class="nt"&gt;--input&lt;/span&gt; &lt;span class="s1"&gt;'{"query": "iphone", "country": "pt", "maxAds": 100}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://api.apify.com/v2/acts/plum_spear~aztec-olx-eu/run-sync-get-dataset-items?token=&amp;lt;YOUR_APIFY_TOKEN&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"query": "iphone", "country": "pt", "maxAds": 100}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What people actually build with this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Price monitoring across markets.&lt;/strong&gt; Track average, minimum and median price for the same term in several countries and get alerted when a listing drops below your target.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-country market research.&lt;/strong&gt; Run the same keyword in Portugal, Poland and Romania and compare price spread, ad volume and business-vs-private mix side by side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lead generation.&lt;/strong&gt; Use the &lt;code&gt;business&lt;/code&gt; flag plus location fields to build prospecting lists of professional sellers by country.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-border resale and arbitrage.&lt;/strong&gt; Compare prices for the same item across markets and flip the gap - the numeric &lt;code&gt;price&lt;/code&gt; field makes this a direct comparison, not manual guesswork.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real estate professionals.&lt;/strong&gt; Scrape apartment, house and land listings by city and region in Poland, Romania, Portugal and beyond.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Car dealers and auto traders.&lt;/strong&gt; Monitor used-car listings across countries and separate dealer ads from private sellers with the &lt;code&gt;business&lt;/code&gt; flag.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;p&gt;Pay-per-event: &lt;strong&gt;$0.15 per 1,000 ads returned&lt;/strong&gt;, plus a minimal actor-start event. No subscription, no monthly minimum, and the same price whether you run it against one country or all six. Apify's free monthly platform credits let you test it against a real query before deciding whether it's worth it.&lt;/p&gt;

&lt;p&gt;For context: if the DIY route above means standing up and maintaining six country-specific scrapers with six currency parsers, $0.15 per 1,000 ads with one consistent schema is the kind of number that stops being a debate fast, especially once you need more than one market at once.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using it from an AI agent (MCP)
&lt;/h2&gt;

&lt;p&gt;If you're wiring this into an agent instead of a script, actors published on Apify, including this one, are reachable through &lt;a href="https://mcp.apify.com" rel="noopener noreferrer"&gt;Apify's MCP server&lt;/a&gt;, which exposes them as callable tools for MCP-compatible clients. Same &lt;code&gt;query&lt;/code&gt; / &lt;code&gt;country&lt;/code&gt; / &lt;code&gt;maxAds&lt;/code&gt; input, no separate integration to write.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;Scraping one OLX country yourself is doable - the Playwright snippet above will get you there for a single market. What gets expensive is doing that six times, keeping six selector sets and six currency parsers alive, and normalizing the results yourself before you can compare countries. That gap is what &lt;a href="https://apify.com/plum_spear/aztec-olx-eu" rel="noopener noreferrer"&gt;OLX Europe Scraper&lt;/a&gt; on Apify closes: give it a &lt;code&gt;query&lt;/code&gt; and a &lt;code&gt;country&lt;/code&gt;, get back the same clean schema across all six markets, priced at $0.15 per 1,000 ads with no monthly commitment.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Precisa monitorizar a sua marca nos assistentes de IA?&lt;/strong&gt;&lt;br&gt;
O &lt;a href="https://geo.azteclab.cloud" rel="noopener noreferrer"&gt;GEO Tracker&lt;/a&gt; analisa a presença da sua empresa no ChatGPT, Gemini, Perplexity, Claude e mais — relatório PDF em 24h.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>webscraping</category>
      <category>python</category>
      <category>javascript</category>
      <category>api</category>
    </item>
  </channel>
</rss>
