<?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: KDatafactory</title>
    <description>The latest articles on DEV Community by KDatafactory (@parkseokjune).</description>
    <link>https://dev.to/parkseokjune</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%2F4021951%2F5028c4a3-1b64-42fa-8cb0-5d4ff5c08af6.png</url>
      <title>DEV Community: KDatafactory</title>
      <link>https://dev.to/parkseokjune</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/parkseokjune"/>
    <language>en</language>
    <item>
      <title>Google Maps scraping works everywhere — except Korea. Here's the Naver Place equivalent.</title>
      <dc:creator>KDatafactory</dc:creator>
      <pubDate>Sun, 19 Jul 2026 23:55:17 +0000</pubDate>
      <link>https://dev.to/parkseokjune/google-maps-scraping-works-everywhere-except-korea-heres-the-naver-place-equivalent-1jj4</link>
      <guid>https://dev.to/parkseokjune/google-maps-scraping-works-everywhere-except-korea-heres-the-naver-place-equivalent-1jj4</guid>
      <description>&lt;p&gt;If you've ever built a lead-gen pipeline, a local-SEO tool, or any "pull every business in this city" scraper, you've probably leaned on Google Maps / the Places API. It works in almost every country on earth.&lt;/p&gt;

&lt;p&gt;Then a client asks for &lt;strong&gt;Korea&lt;/strong&gt;, and the whole stack quietly falls apart.&lt;/p&gt;

&lt;p&gt;I'm a Korean developer, and I want to explain &lt;em&gt;why&lt;/em&gt; Korea is a black hole for every Western location tool — and show you the ~10-line equivalent that actually works here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Google Maps is broken in Korea (it's not your code)
&lt;/h2&gt;

&lt;p&gt;This trips up almost everyone the first time. You run your normal Google Places pipeline against Seoul and get back thin, wrong, or empty data. You assume you misconfigured something. You didn't.&lt;/p&gt;

&lt;p&gt;South Korea has a &lt;strong&gt;decades-old law&lt;/strong&gt; that bars exporting high-precision national map data to servers outside the country. Google has never been allowed to host Korea's detailed map data abroad, so Google Maps in Korea is permanently crippled: no real driving directions, sparse POI coverage, missing hours, missing phone numbers, missing reviews. Locals know this and simply don't use it.&lt;/p&gt;

&lt;p&gt;What they use instead is &lt;strong&gt;Naver&lt;/strong&gt; — Korea's dominant search engine — and specifically &lt;strong&gt;Naver Place&lt;/strong&gt; (네이버 플레이스), which is the real "Google Maps of Korea." Every café, clinic, restaurant, gym, and repair shop in the country lives there with hours, phone, rating, and reviews. Kakao Map is the #2.&lt;/p&gt;

&lt;p&gt;So the practical reality for anyone outside Korea:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Maps / Places API&lt;/strong&gt; → the tool the rest of your product uses → &lt;strong&gt;useless in Korea.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Naver Place&lt;/strong&gt; → where the actual data is → &lt;strong&gt;Korean-only UI, no public API, aggressive anti-bot.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That gap is why "Korea coverage" is a line item Western data vendors quietly skip.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually stops you
&lt;/h2&gt;

&lt;p&gt;It's not just "translate the UI." Three things stack up:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No clean public API.&lt;/strong&gt; Naver's map endpoints are internal, undocumented, and change without notice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Korean-native everything.&lt;/strong&gt; Query semantics, address formats (&lt;code&gt;도로명주소&lt;/code&gt; vs &lt;code&gt;지번&lt;/code&gt;), category taxonomy — all Korean-first. You can't guess the right query as a non-Korean speaker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Datacenter IPs get gated fast.&lt;/strong&gt; Hit it from an AWS range and you'll eat rate-limits and CAPTCHAs within a few hundred requests, especially once you paginate into per-place reviews. You need Korean residential IPs and sane session handling.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of these are hard &lt;em&gt;individually&lt;/em&gt;. Together they're why most people give up and mark Korea "not supported."&lt;/p&gt;

&lt;h2&gt;
  
  
  The ~10-line version that works
&lt;/h2&gt;

&lt;p&gt;I ended up packaging this as a pay-per-result Apify actor so I never have to babysit the anti-bot layer again. Here's the whole thing — no local browser, no proxy setup, just an API call:&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="n"&gt;TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_APIFY_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;ACTOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;naver-place-scraper&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;resp&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;post&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://api.apify.com/v2/acts/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ACTOR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/run-sync-get-dataset-items?token=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;TOKEN&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;json&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;searchQuery&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="c1"&gt;# "Gangnam Station cafe"
&lt;/span&gt;        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxPlaces&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;includeReviews&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxReviewsPerPlace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;place&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;resp&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;place&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="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;place&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;place&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reviewCount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; reviews)&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;The query is the one part you can't fake as a non-Korean speaker. The trick locals use: &lt;strong&gt;&lt;code&gt;location + category&lt;/code&gt;&lt;/strong&gt;. &lt;code&gt;강남역 카페&lt;/code&gt; (Gangnam Station + café), &lt;code&gt;홍대 맛집&lt;/code&gt; (Hongdae + good-eats), &lt;code&gt;제주 흑돼지&lt;/code&gt; (Jeju + black pork). That combo is what makes Naver return a tight, relevant list instead of noise.&lt;/p&gt;

&lt;h2&gt;
  
  
  The output
&lt;/h2&gt;

&lt;p&gt;Everything comes back as clean, English-keyed JSON — no Korean field names to map, no HTML to parse:&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;"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;"Starbucks Gangnam-daero"&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;"Cafe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"phone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1522-3232"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"roadAddress"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"390 Gangnam-daero, Gangnam-gu, Seoul"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"businessHours"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"07:00 - 22: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;"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.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reviewCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1842&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"coordinates"&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="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"lat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;37.4979&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"lng"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;127.0276&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;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="p"&gt;[&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;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="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"date"&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-30"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Clean, quiet on weekday mornings, fast wifi."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"keywords"&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;"clean"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"good for work"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"friendly staff"&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="w"&gt;
  &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="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Name, category, phone, both address formats, hours, rating, review count, coordinates, and paginated per-place reviews with the sentiment keywords Naver attaches. That's the exact shape a lead-gen enrichment step, a local-SEO audit, or a "should we enter this neighborhood" market study needs — and it's the shape Google's API can't give you inside Korea.&lt;/p&gt;

&lt;p&gt;Reviewer identities are never collected — just the review text, rating, date, and keywords.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this is useful
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lead generation&lt;/strong&gt; — every local business in a category/area, with phone + address, for outbound.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local SEO / listings&lt;/strong&gt; — audit and monitor how businesses show up on the platform Koreans actually search.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Market-entry research&lt;/strong&gt; — density, ratings, and review volume by neighborhood before you commit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review mining&lt;/strong&gt; — pull the Korean-language review corpus for a brand or category and run sentiment on it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The honest build-in-public part
&lt;/h2&gt;

&lt;p&gt;This is one of ~30 Korean-platform scrapers I've been building solo — the sites that are big &lt;em&gt;inside&lt;/em&gt; Korea but that nobody outside can crawl: Naver Place, Olive Young (K-beauty rankings), Encar (used cars — huge for exporters), KREAM (sneaker resale), and so on. Same thesis every time: &lt;strong&gt;Korean-native in → clean English JSON out.&lt;/strong&gt; The language wall and the anti-bot wall are exactly what make these worth packaging instead of leaving as a DIY afternoon.&lt;/p&gt;

&lt;p&gt;I'd genuinely like input from people who scrape for a living:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;For lead-gen and local-SEO use cases, is &lt;strong&gt;per-place review pagination&lt;/strong&gt; worth the extra requests, or is the summary (rating + count) enough for most of you?&lt;/li&gt;
&lt;li&gt;On Korean hosts specifically, what's your go-to for &lt;strong&gt;session rotation&lt;/strong&gt; — sticky sessions with retry, or per-request rotation? I've had far better luck with sticky+retry and I'm curious if that matches your experience.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you want to try the Naver Place one, it's here: &lt;strong&gt;&lt;a href="https://apify.com/kdatafactory/naver-place-scraper" rel="noopener noreferrer"&gt;Naver Place Scraper on Apify&lt;/a&gt;&lt;/strong&gt;. First runs are on the free tier, so you can see the JSON before deciding anything.&lt;/p&gt;

&lt;p&gt;And if there's a Korean site you've hit this exact wall on, tell me — that's how I pick what to build next.&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>api</category>
      <category>korea</category>
      <category>leadgen</category>
    </item>
    <item>
      <title>the silent wrong-ranking bug</title>
      <dc:creator>KDatafactory</dc:creator>
      <pubDate>Wed, 15 Jul 2026 01:01:51 +0000</pubDate>
      <link>https://dev.to/parkseokjune/the-silent-wrong-ranking-bug-2689</link>
      <guid>https://dev.to/parkseokjune/the-silent-wrong-ranking-bug-2689</guid>
      <description>&lt;h2&gt;
  
  
  A customer told me my scraper's #1 product was wrong. They were right — and the bug was nastier than a crash.
&lt;/h2&gt;

&lt;p&gt;I run a small catalogue of scrapers for Korean e-commerce and content platforms. Last week a customer using my Olive Young (a huge Korean beauty retailer) best-seller scraper sent me a short, polite message:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The ranking looks off. Your #1 is a probiotics supplement, but the real #1 on the site is a MEDIHEAL mask sheet."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;She was completely right. And the reason why is the kind of bug I now think about a lot.&lt;/p&gt;

&lt;h3&gt;
  
  
  It wasn't down. It wasn't empty. It was &lt;em&gt;plausibly wrong.&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;My nightly health-checks were all green. Every run succeeded, returned 100 items, and every field was populated — real product names, real prices, real images. If you glanced at the output it looked perfect.&lt;/p&gt;

&lt;p&gt;The problem: the &lt;em&gt;order&lt;/em&gt; was wrong, and the products were the wrong set. My scraper's "#1 best-seller" was a diet-probiotics capsule. The site's actual #1 was a face mask.&lt;/p&gt;

&lt;p&gt;Here's what happened. Olive Young serves ranking data from &lt;strong&gt;two different places&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;product-ranking-service.oliveyoung.com/v1/pages/ranking/sales/products&lt;/code&gt; — an internal "sales ranking" microservice.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;global.oliveyoung.com/display/product/best-seller/order-best&lt;/code&gt; — the endpoint the site's own &lt;strong&gt;"Best" page&lt;/strong&gt; actually renders.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My scraper called the first one. The site shows the second one. Both return real, valid-looking products in a real order — they're just &lt;strong&gt;different boards&lt;/strong&gt;. So the scraper produced data that passed every automated check and was still wrong to any human who'd looked at the actual page.&lt;/p&gt;

&lt;h3&gt;
  
  
  The scary part: it's a whole &lt;em&gt;class&lt;/em&gt; of bug
&lt;/h3&gt;

&lt;p&gt;That one report made me stop and audit my whole catalogue against the live sites — not "does it return data" but "does the top-N match what a real user sees right now."&lt;/p&gt;

&lt;p&gt;I found the same shape more than once. The common trigger was innocent-looking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// looks harmless&lt;/span&gt;
&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;category-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;// "" = give me the overall ranking, right?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Except the site had quietly changed so that an &lt;strong&gt;empty/absent parameter no longer returns the ranking&lt;/strong&gt; — it returns an unranked default set (sorted by internal product id, every rating 0, half of them sold out). No error. Just a plausible-looking list in the wrong order. A couple of my scrapers were shipping &lt;em&gt;that&lt;/em&gt; as "the best-seller ranking."&lt;/p&gt;

&lt;p&gt;Other variants I found the same day:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;6-day-stale&lt;/strong&gt; cached list served under a no-filter request while the real ranking lived under one specific filter id;&lt;/li&gt;
&lt;li&gt;a marketplace where a boolean &lt;code&gt;used&lt;/code&gt; flag was &lt;strong&gt;inverted&lt;/strong&gt; (the API's &lt;code&gt;2&lt;/code&gt; meant &lt;em&gt;new&lt;/em&gt;, not &lt;code&gt;1&lt;/code&gt;), so every "new/used" label was backwards;&lt;/li&gt;
&lt;li&gt;a webtoon ranking labelled "by rating" that was actually the site's default &lt;em&gt;popularity&lt;/em&gt; order.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these crashed. None returned zero rows. All of them would sail through a "is it up?" monitor forever.&lt;/p&gt;

&lt;h3&gt;
  
  
  The lesson I wish I'd internalised earlier
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;"It runs and returns data" is not "it's correct."&lt;/strong&gt; For anything ranked, the only real test is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Fetch what a real user sees on the actual page, and compare your top-N to it — order and identity, not just field presence.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Concretely, what I changed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Verify against the human-facing view.&lt;/strong&gt; For each ranking scraper I now diff the top ~5 against the live page the user would look at, not just against "an endpoint that returns products."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prefer the endpoint the site itself renders.&lt;/strong&gt; If you find a cleaner-looking internal microservice, double-check it's the &lt;em&gt;same board&lt;/em&gt; the page shows. A different host is a red flag.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add sanity guards, not just presence checks.&lt;/strong&gt; Fail (loudly) the run if the list is near-monotonic by id, or every rating is 0, or the sold-out share is absurd — those are the fingerprints of a "silent default" response.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fix for Olive Young itself was small once I understood it — point at &lt;code&gt;/display/product/best-seller/order-best&lt;/code&gt; with the params the Best page sends, map the real field names, and now the scraper's #1 is MEDIHEAL, matching the site. (Bonus: the correct endpoint even carries review counts the old one didn't.)&lt;/p&gt;

&lt;h3&gt;
  
  
  The thing I keep coming back to
&lt;/h3&gt;

&lt;p&gt;The most valuable message I got all month was one sentence from a user pointing out that my #1 was wrong. It didn't just fix one scraper — it exposed a blind spot in how I was validating &lt;em&gt;all&lt;/em&gt; of them.&lt;/p&gt;

&lt;p&gt;If you scrape ranked data: go look at one of your outputs next to the real page right now. Not the schema. The order.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I maintain a set of Korean-platform scrapers (&lt;a href="https://apify.com/kdatafactory" rel="noopener noreferrer"&gt;kdatafactory&lt;/a&gt;). If you've hit this "plausibly wrong" class of bug, I'd love to hear how you catch it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>ecommerce</category>
      <category>datascience</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I pulled the 800 newest cars on Korea's #1 used-car site. The map didn't match the market.</title>
      <dc:creator>KDatafactory</dc:creator>
      <pubDate>Mon, 13 Jul 2026 07:19:27 +0000</pubDate>
      <link>https://dev.to/parkseokjune/i-pulled-the-800-newest-cars-on-koreas-1-used-car-site-the-map-didnt-match-the-market-4f71</link>
      <guid>https://dev.to/parkseokjune/i-pulled-the-800-newest-cars-on-koreas-1-used-car-site-the-map-didnt-match-the-market-4f71</guid>
      <description>&lt;p&gt;&lt;em&gt;A snapshot from Encar (encar.com), Korea's largest used-car marketplace — 223,000 live listings — pulled 2026-07-13. Public listing data only; no personal or dealer data.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Korea is one of the world's larger used-car export sources, and if you're on the buying end of that trade, the question that matters isn't &lt;em&gt;"what's for sale"&lt;/em&gt; — it's &lt;em&gt;"where is listing activity concentrated right now?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So I took the simplest possible cut: the &lt;strong&gt;800 most-recently-listed cars&lt;/strong&gt; on Encar, and compared their shape to the &lt;strong&gt;whole 223K-listing market&lt;/strong&gt;. One honesty note up front: "most-recently-listed" includes re-lists and bumped ads, not only cars newly entering the market — so everything below is about &lt;strong&gt;listing activity&lt;/strong&gt;, not proven new supply. The pull took one run of &lt;a href="https://apify.com/kdatafactory/encar-scraper" rel="noopener noreferrer"&gt;encar-scraper&lt;/a&gt;, an Apify actor I maintain that turns Encar's listing feed into clean JSON. If fresh listing activity looked like the standing market, the two should line up.&lt;/p&gt;

&lt;p&gt;They didn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  One province is running 4.5× above its weight
&lt;/h2&gt;

&lt;p&gt;If you work this trade you already know the southeast runs Korea's used-car exports. What I hadn't seen before is that quantified against standing stock. Break the newest 800 down by region and compare each region's share of &lt;em&gt;fresh listings&lt;/em&gt; to its share of &lt;em&gt;total inventory&lt;/em&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Region&lt;/th&gt;
&lt;th&gt;Share of newest listings&lt;/th&gt;
&lt;th&gt;Share of total market&lt;/th&gt;
&lt;th&gt;Index&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;경남 South Gyeongsang&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;13.0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.9%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4.5×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;대구 Daegu&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;16.5%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;9.0%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.8×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;인천 Incheon&lt;/td&gt;
&lt;td&gt;8.8%&lt;/td&gt;
&lt;td&gt;6.1%&lt;/td&gt;
&lt;td&gt;1.5×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;서울 Seoul&lt;/td&gt;
&lt;td&gt;11.3%&lt;/td&gt;
&lt;td&gt;10.3%&lt;/td&gt;
&lt;td&gt;1.1×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;부산 Busan&lt;/td&gt;
&lt;td&gt;8.0%&lt;/td&gt;
&lt;td&gt;9.7%&lt;/td&gt;
&lt;td&gt;0.8×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;경기 Gyeonggi&lt;/td&gt;
&lt;td&gt;31.4%&lt;/td&gt;
&lt;td&gt;42.7%&lt;/td&gt;
&lt;td&gt;0.7×&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg06w3w3j8s2upwr6gczr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg06w3w3j8s2upwr6gczr.png" alt=" " width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;South Gyeongsang holds under &lt;strong&gt;3%&lt;/strong&gt; of Korea's used-car inventory — but accounted for &lt;strong&gt;13%&lt;/strong&gt; of the freshest listings in this snapshot. Daegu, next door, ran at nearly double its weight. Together, &lt;strong&gt;Daegu + South Gyeongsang = 29.5% of fresh listing activity from 11.9% of the standing stock — a 2.5× concentration.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Meanwhile Gyeonggi — the Seoul-belt province that holds &lt;strong&gt;43%&lt;/strong&gt; of all inventory — &lt;em&gt;under&lt;/em&gt;-indexed (0.7×). The big pool is sitting still; the southeast corner is the one churning.&lt;/p&gt;

&lt;p&gt;That corner is also where Korea's export machine lives — Busan port and the Daegu auto-export complex. Two caveats before reading it that way: this is one cross-sectional snapshot, so it can't prove causation, and a handful of high-volume southeastern dealers re-bumping their inventory could account for part of the anomaly. Still, "which region is &lt;em&gt;actively churning&lt;/em&gt; listings" is a signal worth watching if you source there — and it's not where a population map would point you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's over-represented in fresh listings — and what actually exports
&lt;/h2&gt;

&lt;p&gt;The single most-listed model across the entire fresh feed was the &lt;strong&gt;BMW 5-Series (G30)&lt;/strong&gt; — 19 units, over-indexing at 1.13× (Mercedes, for what it's worth, &lt;em&gt;under&lt;/em&gt;-indexed at 0.66× — this is a BMW story, not a "German cars" story). &lt;strong&gt;Genesis&lt;/strong&gt; over-indexed at 1.3×, with 47 units of GV70, GV80, and G80 inside the top 15.&lt;/p&gt;

&lt;p&gt;Top fresh models:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;BMW 5-Series (G30) — 19&lt;/li&gt;
&lt;li&gt;Genesis GV70 — 18&lt;/li&gt;
&lt;li&gt;Hyundai Grandeur IG — 18&lt;/li&gt;
&lt;li&gt;Kia Ray — 16&lt;/li&gt;
&lt;li&gt;Hyundai Avante AD — 15&lt;/li&gt;
&lt;li&gt;Genesis GV80 — 15&lt;/li&gt;
&lt;li&gt;Kia Carnival (4th gen) — 15&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To be clear, this list is a mix: the 5-Series, Genesis SUVs, and Grandeur are export-relevant metal, while the Ray, Avante, and QM6 are domestic-turnover cars that mostly stay home. The signal isn't "everything fresh is export-bound" — it's that the export-relevant segment (premium sedans, Genesis SUVs) is over-represented in fresh listing activity rather than diluted by it. Median fresh asking price: &lt;strong&gt;₩20.9M (~$15,100)&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The glut that &lt;em&gt;wasn't&lt;/em&gt; in the data
&lt;/h2&gt;

&lt;p&gt;Every few months there's a headline about Korea's &lt;strong&gt;used-EV glut&lt;/strong&gt; flooding the second-hand market. In this snapshot, it didn't show up.&lt;/p&gt;

&lt;p&gt;Pure electric cars were &lt;strong&gt;1.9%&lt;/strong&gt; of the freshest 800 listings — &lt;em&gt;lower&lt;/em&gt; than their &lt;strong&gt;3.1%&lt;/strong&gt; share of standing inventory (a 0.6× index). Whatever is churning through Korea's used market right now, it is overwhelmingly &lt;strong&gt;gasoline (60%) and diesel (24%)&lt;/strong&gt;. One snapshot doesn't debunk a narrative, but if EVs were pouring in, this is where it would show — and it didn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  The age band lines up with export demand
&lt;/h2&gt;

&lt;p&gt;Fresh listings cluster at &lt;strong&gt;2019–2023 model years — 3 to 7 years old&lt;/strong&gt;. I won't claim intent behind that; a healthy used market naturally churns cars at that age. But it happens to be the band most export destinations' age caps target, which makes the fresh feed unusually usable for sourcing: for buyers, the overlap matters more than the reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce this yourself (~5 minutes, free)
&lt;/h2&gt;

&lt;p&gt;Everything above is one actor run away. On &lt;a href="https://apify.com/kdatafactory/encar-scraper" rel="noopener noreferrer"&gt;encar-scraper&lt;/a&gt;, this input pulls the same fresh-listings sample:&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;"carType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"all"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"newest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxItems"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;800&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;Each record comes back as structured JSON — &lt;code&gt;make&lt;/code&gt;, &lt;code&gt;model&lt;/code&gt;, &lt;code&gt;year&lt;/code&gt;, &lt;code&gt;price_krw&lt;/code&gt;, &lt;code&gt;mileage_km&lt;/code&gt;, &lt;code&gt;fuel&lt;/code&gt;, &lt;code&gt;region&lt;/code&gt;, &lt;code&gt;url&lt;/code&gt;, &lt;code&gt;image_url&lt;/code&gt; — so the region/fuel/price group-bys in this post are a few lines of pandas (or a pivot table; CSV/Excel export works too). Apify's free plan includes $5 of monthly credit, and at $2 per 1,000 results that covers this entire analysis with room to spare — no credit card needed.&lt;/p&gt;

&lt;p&gt;Want the second half of the market? Korea's #2 marketplace runs on the same contract: &lt;a href="https://apify.com/kdatafactory/kbchachacha-scraper" rel="noopener noreferrer"&gt;kbchachacha-scraper&lt;/a&gt; returns identically-shaped records (~168k more listings), so the two feeds concat into one dataframe cleanly. If your stack talks MCP, both actors also work as AI-agent tools out of the box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I'm posting this
&lt;/h2&gt;

&lt;p&gt;I run &lt;a href="https://parkseokjune.github.io/kdatafactory-hub/" rel="noopener noreferrer"&gt;a suite of 24 Korean-platform scrapers&lt;/a&gt; — Korean sites are largely invisible to Western data tools, and I keep finding stories like this one sitting in plain sight. Everything here is public, non-authenticated listing data: the actor reads Encar's own listing feed and never touches logins, private endpoints, or personal information.&lt;/p&gt;

&lt;p&gt;The raw listings aren't the interesting part — anyone can browse those a page at a time. But structured, one afternoon's pull surfaces a regional churn anomaly, a premium-listing skew, and an EV glut that failed to appear — patterns that are hard to see at a glance even if you live in this market.&lt;/p&gt;

&lt;p&gt;Next on my list is turning this into a live export-sourcing feed: newest listings by make and region, with destination age-cap eligibility computed per car from model year. If you source cars out of Korea, tell me in the comments which filters would actually matter to you — that's what gets built first.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Method: 800 newest-listed cars (Encar public list API, sort = most-recently-listed) vs whole-market count baselines, 2026-07-13, from a Korean IP. Single cross-sectional snapshot, not a time series — every comparison here is "fresh listings vs standing stock," never "this month vs last." "Fresh" includes re-listed ads. Public listing attributes only; no dealer or personal data collected.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>ecommerce</category>
      <category>automation</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Current Pocamarket median asks by member (Korea's source market, not eBay) — Jul 2026</title>
      <dc:creator>KDatafactory</dc:creator>
      <pubDate>Sun, 12 Jul 2026 23:30:32 +0000</pubDate>
      <link>https://dev.to/parkseokjune/current-pocamarket-median-asks-by-member-koreas-source-market-not-ebay-jul-2026-49p3</link>
      <guid>https://dev.to/parkseokjune/current-pocamarket-median-asks-by-member-koreas-source-market-not-ebay-jul-2026-49p3</guid>
      <description>&lt;p&gt;Most price references we use (eBay solds, Mercari) are &lt;em&gt;Western resale&lt;/em&gt; prices — but cards originate and price-discover in Korea first. I pulled current listings from Pocamarket (포카마켓, Korea's dedicated photocard marketplace, ~477k cards listed) and computed the &lt;strong&gt;median asking price per member&lt;/strong&gt; from the newest listings. Prices converted from KRW at roughly current rates. Sharing in case it helps you price sales/trades:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Group&lt;/th&gt;
&lt;th&gt;Member&lt;/th&gt;
&lt;th&gt;Listings&lt;/th&gt;
&lt;th&gt;Median ask&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Wishlist/card&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TXT&lt;/td&gt;
&lt;td&gt;Yeonjun&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;$5.60&lt;/td&gt;
&lt;td&gt;$2.50 – $17.50&lt;/td&gt;
&lt;td&gt;1,561&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NCT&lt;/td&gt;
&lt;td&gt;Mark&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;$4.55&lt;/td&gt;
&lt;td&gt;$2.50 – $17.50&lt;/td&gt;
&lt;td&gt;402&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IVE&lt;/td&gt;
&lt;td&gt;Leeseo&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;$4.90&lt;/td&gt;
&lt;td&gt;$2.50 – $21.00&lt;/td&gt;
&lt;td&gt;796&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BOYNEXTDOOR&lt;/td&gt;
&lt;td&gt;Taesan&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;$3.50&lt;/td&gt;
&lt;td&gt;$2.50 – &lt;strong&gt;$66.50&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;1,358&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NCT&lt;/td&gt;
&lt;td&gt;Doyoung&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;$3.50&lt;/td&gt;
&lt;td&gt;$2.50 – $9.80&lt;/td&gt;
&lt;td&gt;1,004&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;amp;TEAM&lt;/td&gt;
&lt;td&gt;EJ&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;$3.50&lt;/td&gt;
&lt;td&gt;$2.50 – $45.50&lt;/td&gt;
&lt;td&gt;348&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NCT&lt;/td&gt;
&lt;td&gt;Yushi&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;$3.00&lt;/td&gt;
&lt;td&gt;$2.50 – $9.80&lt;/td&gt;
&lt;td&gt;1,442&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RIIZE&lt;/td&gt;
&lt;td&gt;Anton&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;$2.50&lt;/td&gt;
&lt;td&gt;$2.50 – $28.00&lt;/td&gt;
&lt;td&gt;290&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RIIZE&lt;/td&gt;
&lt;td&gt;Wonbin&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;$2.50&lt;/td&gt;
&lt;td&gt;$2.50 – $17.50&lt;/td&gt;
&lt;td&gt;686&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RESCENE&lt;/td&gt;
&lt;td&gt;Zena&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;$8.40&lt;/td&gt;
&lt;td&gt;$3.50 – $17.50&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few things that jumped out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Demand and price are inverted on the biggest names.&lt;/strong&gt; NCT Yushi and Doyoung, TXT Yeonjun, BOYNEXTDOOR Taesan all carry huge wishlist counts (1,000–1,560+ per card) but median asks sit at $3–5. That looks like &lt;em&gt;liquidity&lt;/em&gt; — lots of supply meeting lots of demand — not weak demand. The cheap median is the floor, not the ceiling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The range is where the money is.&lt;/strong&gt; Taesan's median is $3.50 but the top listing is &lt;strong&gt;$66.50&lt;/strong&gt;, and &amp;amp;TEAM EJ runs $2.50 up to $45.50. Medians tell you the common card; the max tells you a chase/grail is in the pool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rookie-group economics are flipped.&lt;/strong&gt; RESCENE Zena has the &lt;em&gt;highest&lt;/em&gt; median ($8.40) but almost no wishlist activity (4/card) — thin market, sellers asking high because there's little to compare against. Higher risk if you're buying to flip.&lt;/li&gt;
&lt;li&gt;These are &lt;strong&gt;asking&lt;/strong&gt; prices from the newest listings, small samples (8–24 each), so treat them as a reference floor, not sold comps.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If people find this useful I can post an updated table weekly, or break one group down by member/era/card type. &lt;strong&gt;Which group should I do next?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>ecommerce</category>
      <category>automation</category>
    </item>
    <item>
      <title>The K-pop photocard market has a $2B blind spot: nobody's pricing the Korean source</title>
      <dc:creator>KDatafactory</dc:creator>
      <pubDate>Fri, 10 Jul 2026 07:16:54 +0000</pubDate>
      <link>https://dev.to/parkseokjune/the-k-pop-photocard-market-has-a-2b-blind-spot-nobodys-pricing-the-korean-source-2cfp</link>
      <guid>https://dev.to/parkseokjune/the-k-pop-photocard-market-has-a-2b-blind-spot-nobodys-pricing-the-korean-source-2cfp</guid>
      <description>&lt;p&gt;If you've ever tried to price a K-pop photocard, you know the pain: the same card is $8 on one app, $45 on a reseller's Depop, and "DM for price" everywhere else. There's a $2B+ collectibles market here with almost no price transparency.&lt;/p&gt;

&lt;p&gt;Every price-guide app and reseller tool I've looked at solves this the same way: scrape eBay and Mercari. Which makes sense if you're in the US. But it misses something big — the cards originate and price-discover in Korea first. A card trades on the Korean market for weeks before the Western resale price settles. If you're only watching eBay, you're reading yesterday's news.&lt;/p&gt;

&lt;p&gt;So I went to the source.&lt;/p&gt;

&lt;p&gt;The source market nobody's scraping&lt;br&gt;
Pocamarket (포카마켓) is Korea's dedicated photocard marketplace — 477,000+ cards listed, organized by group, member, era, and card type (album / fansign / POB / lucky-draw). It's where Korean collectors buy, sell, and set the reference price. For a huge fraction of cards, this is the primary market and eBay is the derivative one.&lt;/p&gt;

&lt;p&gt;The interesting part for anyone building tooling: it has a clean public catalog API. No login needed for the listing data.&lt;/p&gt;

&lt;p&gt;GET &lt;a href="https://pocamarket.com/apis/card/gb/v2/search?q=" rel="noopener noreferrer"&gt;https://pocamarket.com/apis/card/gb/v2/search?q=&lt;/a&gt;&amp;amp;page=&lt;br&gt;
Returns per-card, structured: group, member, price, trade type (sale vs. trade), stock, wish count, sales volume, and an image. Everything you need to build a price index — per member, per era, per card type — sourced from where the price actually forms.&lt;/p&gt;

&lt;p&gt;What you can do with it&lt;br&gt;
Arbitrage signal. Korean price vs. eBay/Mercary price = the spread. When a card is cheap on Pocamarket and hot on Western resale, that's a sourcing signal before the Western price catches up.&lt;br&gt;
A real price index. sales_volume + wish_count per card let you build a demand-weighted index per member/era — the "StockX for photocards" that collectors keep asking for but nobody's built on source-market data.&lt;br&gt;
Inventory pricing. If you resell, pricing against the origin market instead of guessing beats the DM-for-price economy.&lt;br&gt;
A couple of honest caveats&lt;br&gt;
Prices are the catalog's, not seller identities. The scraper I built collects card + price data only — no seller nicknames, IDs, or profiles. (Both a privacy rule and a legal one — don't scrape people.)&lt;br&gt;
card_type and era need the detail endpoint, not just search. The base catalog gives you the price signal; the per-card detail call adds the metadata.&lt;br&gt;
Sort is fixed to newest-first on the public API — server-side price/popularity sort params are ignored, so you sort client-side.&lt;br&gt;
If you don't want to maintain the scraper&lt;br&gt;
I packaged this as a ready-to-run Apify actor — it handles the pagination, the detail enrichment, and the privacy filtering, priced per result:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apify.com/kdatafactory/pocamarket-scraper" rel="noopener noreferrer"&gt;https://apify.com/kdatafactory/pocamarket-scraper&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(Want the retail side too — K-pop album/goods prices in USD from Ktown4u's global store? There's a matching actor: &lt;a href="https://apify.com/kdatafactory/ktown4u-scraper" rel="noopener noreferrer"&gt;https://apify.com/kdatafactory/ktown4u-scraper&lt;/a&gt;. Origin-market secondary prices + origin-market retail prices is the full picture.)&lt;/p&gt;

&lt;p&gt;But the endpoint above is the real unlock. Whether you build it yourself or not, the point stands: if you're pricing K-pop collectibles off Western marketplaces only, you're trading on lagged data. The source market is right there, and it's structured.&lt;/p&gt;

&lt;p&gt;Happy to answer questions on the data shape or the index math in the comments.&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>api</category>
      <category>database</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How I track K-beauty prices for dropshipping without writing a scraper</title>
      <dc:creator>KDatafactory</dc:creator>
      <pubDate>Thu, 09 Jul 2026 02:54:15 +0000</pubDate>
      <link>https://dev.to/parkseokjune/how-i-track-k-beauty-prices-for-dropshipping-without-writing-a-scraper-2e9e</link>
      <guid>https://dev.to/parkseokjune/how-i-track-k-beauty-prices-for-dropshipping-without-writing-a-scraper-2e9e</guid>
      <description>&lt;p&gt;If you sell K-beauty, you already know the pain: &lt;strong&gt;Olive Young is where the trends start&lt;/strong&gt;, but the site is in Korean, prices are in won, and the best-seller list reshuffles constantly. Checking it by hand every morning to see what's climbing and what went on sale is a chore — and by the time you notice a product blowing up, everyone else has too.&lt;/p&gt;

&lt;p&gt;Here's the automated setup I use to get a daily K-beauty price + ranking feed &lt;strong&gt;in English and USD&lt;/strong&gt;, without maintaining any scraping code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea
&lt;/h2&gt;

&lt;p&gt;Instead of scraping Olive Young yourself (their site actively blocks bots, so this is a maintenance treadmill), you run a hosted scraper on a schedule and pipe the output wherever you want — a spreadsheet, a Slack message, a webhook into your store.&lt;/p&gt;

&lt;p&gt;I use the &lt;strong&gt;Olive Young Global scraper&lt;/strong&gt; on Apify because its output is already normalized: English names, USD prices, discount %, rating, review count, and the best-seller rank. Pay-per-result, so a daily pull of the top 100 costs cents.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup (about 10 minutes, no code)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Grab the best-seller ranking.&lt;/strong&gt; The scraper's &lt;code&gt;ranking&lt;/code&gt; mode returns the current top sellers. Input is just:&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;"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;"ranking"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"maxItems"&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;You get back rows like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csvs"&gt;&lt;code&gt;&lt;span class="k"&gt;rank&lt;/span&gt;  &lt;span class="k"&gt;name&lt;/span&gt;                                       &lt;span class="k"&gt;brand&lt;/span&gt;      &lt;span class="k"&gt;price&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;usd&lt;/span&gt;  &lt;span class="k"&gt;sale&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;usd&lt;/span&gt;  &lt;span class="k"&gt;rating&lt;/span&gt;  &lt;span class="k"&gt;reviews&lt;/span&gt;
&lt;span class="mf"&gt;1&lt;/span&gt;     &lt;span class="k"&gt;Madagascar&lt;/span&gt; &lt;span class="k"&gt;Centella&lt;/span&gt; &lt;span class="k"&gt;Hyalu&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="k"&gt;Cica&lt;/span&gt; &lt;span class="k"&gt;Sun&lt;/span&gt; &lt;span class="k"&gt;Serum&lt;/span&gt;   &lt;span class="k"&gt;SKIN&lt;/span&gt;&lt;span class="mf"&gt;1004&lt;/span&gt;   &lt;span class="mf"&gt;28.00&lt;/span&gt;      &lt;span class="mf"&gt;22.40&lt;/span&gt;     &lt;span class="mf"&gt;4.9&lt;/span&gt;     &lt;span class="mf"&gt;9958&lt;/span&gt;
&lt;span class="mf"&gt;2&lt;/span&gt;     &lt;span class="k"&gt;Advanced&lt;/span&gt; &lt;span class="k"&gt;Snail&lt;/span&gt; &lt;span class="mf"&gt;96&lt;/span&gt; &lt;span class="k"&gt;Mucin&lt;/span&gt; &lt;span class="k"&gt;Power&lt;/span&gt; &lt;span class="k"&gt;Essence&lt;/span&gt;      &lt;span class="k"&gt;COSRX&lt;/span&gt;      &lt;span class="mf"&gt;22.00&lt;/span&gt;      &lt;span class="err"&gt;—&lt;/span&gt;         &lt;span class="mf"&gt;4.8&lt;/span&gt;     &lt;span class="mf"&gt;41&lt;/span&gt;&lt;span class="k"&gt;k&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Put it on a schedule.&lt;/strong&gt; Apify has a built-in scheduler — set it to run every morning. Now you have a daily snapshot with no effort.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Send it somewhere you'll actually look.&lt;/strong&gt; Wire the run's output to Google Sheets (there's a one-click integration) so each day appends a new tab, or to a Slack/Discord webhook. Now you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Spot risers&lt;/strong&gt; — a product jumping from rank 40 to rank 8 overnight is a sourcing signal before it's saturated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Catch sales&lt;/strong&gt; — when &lt;code&gt;sale_usd&lt;/code&gt; drops on something you resell, you know your margin math changed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Price competitively&lt;/strong&gt; — you're pricing against the actual source-market price, in your currency, automatically.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. (Optional) Track a specific niche.&lt;/strong&gt; Swap &lt;code&gt;ranking&lt;/code&gt; for &lt;code&gt;search&lt;/code&gt; mode with a keyword — &lt;code&gt;snail mucin&lt;/code&gt;, &lt;code&gt;sunscreen&lt;/code&gt;, &lt;code&gt;pdrn&lt;/code&gt; — to watch just your category instead of the whole store.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why USD matters here
&lt;/h2&gt;

&lt;p&gt;Most Korean-data tools hand you won prices and Korean product names, and you're left doing currency conversion and Google Translate before the data is usable. Pulling from Olive Young's &lt;strong&gt;global&lt;/strong&gt; storefront means the numbers already come out in USD with English names — the difference between "data I have to clean" and "data I can paste into a pricing sheet."&lt;/p&gt;

&lt;h2&gt;
  
  
  The tool
&lt;/h2&gt;

&lt;p&gt;The scraper I use is here: &lt;strong&gt;&lt;a href="https://apify.com/kdatafactory/oliveyoung-scraper" rel="noopener noreferrer"&gt;https://apify.com/kdatafactory/oliveyoung-scraper&lt;/a&gt;&lt;/strong&gt; — there's a free tier of platform credit to test it, and the input is exactly what's shown above. If you sell K-fashion instead, the same setup works with the Musinsa one: &lt;strong&gt;&lt;a href="https://apify.com/kdatafactory/musinsa-scraper" rel="noopener noreferrer"&gt;https://apify.com/kdatafactory/musinsa-scraper&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The whole point is to stop checking a Korean website by hand every morning. Set it once, read the feed, source faster than the people still doing it manually. Questions welcome in the comments.&lt;/p&gt;

</description>
      <category>api</category>
      <category>ecommerce</category>
      <category>nocode</category>
      <category>automation</category>
    </item>
    <item>
      <title>Scraping Korea's biggest stores: the hidden JSON APIs behind Olive Young, Musinsa &amp; Naver</title>
      <dc:creator>KDatafactory</dc:creator>
      <pubDate>Wed, 08 Jul 2026 23:48:53 +0000</pubDate>
      <link>https://dev.to/parkseokjune/scraping-koreas-biggest-stores-the-hidden-json-apis-behind-olive-young-musinsa-naver-4h2g</link>
      <guid>https://dev.to/parkseokjune/scraping-koreas-biggest-stores-the-hidden-json-apis-behind-olive-young-musinsa-naver-4h2g</guid>
      <description>&lt;p&gt;Korean e-commerce is one of the richest, least-scraped data sources on the web. K-beauty and K-fashion drive billions in global demand, but almost every tutorial you find scrapes Amazon or Shopify — because the Korean platforms sit behind a double wall: &lt;strong&gt;the language, and some genuinely annoying anti-bot setups.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I spent a while getting reliable, structured data out of the three that matter most — &lt;strong&gt;Olive Young&lt;/strong&gt; (K-beauty), &lt;strong&gt;Musinsa&lt;/strong&gt; (K-fashion), and &lt;strong&gt;Naver Place&lt;/strong&gt; (local businesses + reviews). Here's what actually worked, the parts that fought back, and the rules I set for myself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule #1: never parse the HTML if the site talks JSON to itself
&lt;/h2&gt;

&lt;p&gt;Every one of these is a JavaScript SPA. If you &lt;code&gt;GET&lt;/code&gt; the page and run Cheerio over it, you get a shell. The trick is the same everywhere: open the network tab, reload, and watch what the frontend calls to fill itself in. That call is almost always a clean JSON endpoint — the same data the page renders, minus the parsing pain and minus 90% of the breakage when they reskin the site.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Musinsa&lt;/strong&gt; was the friendliest. One endpoint powers both category rankings and keyword search:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET https://api.musinsa.com/api2/dp/v1/plp/goods
    ?gf=A&amp;amp;sortCode=POPULAR&amp;amp;caller=CATEGORY&amp;amp;category=001&amp;amp;page=1&amp;amp;size=60
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swap &lt;code&gt;caller=CATEGORY&amp;amp;category=001&lt;/code&gt; for &lt;code&gt;caller=SEARCH&amp;amp;keyword=&amp;lt;term&amp;gt;&lt;/code&gt; and you've got search. The response hands you &lt;code&gt;goodsNo&lt;/code&gt;, &lt;code&gt;brandName&lt;/code&gt;, &lt;code&gt;normalPrice&lt;/code&gt;, &lt;code&gt;finalPrice&lt;/code&gt;, &lt;code&gt;saleRate&lt;/code&gt;, &lt;code&gt;reviewCount&lt;/code&gt;, &lt;code&gt;reviewScore&lt;/code&gt; — everything, already structured. No auth. (Their &lt;code&gt;reviewScore&lt;/code&gt; is 0–100, so divide by 20 if you want a 0–5 rating.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Olive Young&lt;/strong&gt; was two services once I switched to their &lt;strong&gt;global&lt;/strong&gt; storefront (&lt;code&gt;global.oliveyoung.com&lt;/code&gt;) — which is actually better for most people, because it returns &lt;strong&gt;English product names and USD prices&lt;/strong&gt; instead of Korean + KRW:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET  product-ranking-service.oliveyoung.com/v1/pages/ranking/sales/products   # best sellers
POST cbe-external-api.oliveyoung.com/display/v1/search/products/unified-search # search
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The domestic site (&lt;code&gt;www.oliveyoung.co.kr&lt;/code&gt;) 403'd my datacenter IP immediately; the global one didn't. Worth checking both when a Korean site blocks you — the international storefront is often more permissive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Naver Place&lt;/strong&gt; was the stubborn one. The clean data lives in the Apollo cache embedded in the search results HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET https://search.naver.com/search.naver?where=nexearch&amp;amp;query=강남역 카페
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pull the &lt;code&gt;__APOLLO_STATE__&lt;/code&gt; blob out of the page, and you get &lt;code&gt;PlaceListBusinessesItem&lt;/code&gt; nodes with name, category, address, phone, rating, review count — plus a few review snippets linked from each place. The deeper per-place review API (&lt;code&gt;pcmap-api.place.naver.com/graphql&lt;/code&gt;) exists, but it &lt;strong&gt;CAPTCHA-walls datacenter IPs&lt;/strong&gt; with an HTTP 405. More on that below.&lt;/p&gt;

&lt;h2&gt;
  
  
  The parts that fought back
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Datacenter IPs get filtered.&lt;/strong&gt; All three tolerate a light touch from a normal IP but clamp down on cloud ranges fast. The fix is boring: residential proxies (Korea-geo for Naver especially). What matters in code is that a hung proxy tunnel shouldn't take down the whole run — wrap the fetch so a proxy timeout &lt;strong&gt;falls back to a direct connection&lt;/strong&gt; instead of throwing. That one change turned a lot of silent zero-result runs into successful ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Succeeded with 0 results" is a lie you tell yourself.&lt;/strong&gt; My first Naver cloud run reported success and returned nothing, with no error in the logs — because the code swallowed a proxy timeout and exited cleanly. If a run collects nothing, &lt;strong&gt;throw.&lt;/strong&gt; A paid data tool that silently returns empty is worse than one that fails loudly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pagination isn't always real pagination.&lt;/strong&gt; Naver's search page repeats results after ~15 businesses; true depth requires the GraphQL route (and a residential IP). Know where your easy path ends so you don't ship something that looks paginated but isn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule #2: public, non-personal data only
&lt;/h2&gt;

&lt;p&gt;This is the line I don't cross, and I'd encourage anyone scraping reviews to hold it too: &lt;strong&gt;I never collect reviewer identity.&lt;/strong&gt; Review objects carry &lt;code&gt;text&lt;/code&gt;, &lt;code&gt;rating&lt;/code&gt;, &lt;code&gt;date&lt;/code&gt;, and &lt;code&gt;keywords&lt;/code&gt; — never nicknames, profile URLs, or user IDs. Business phone numbers and addresses are fine (they're public business info); a person's name attached to a review is not. It keeps the data useful for sentiment/market analysis without turning into a privacy problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The output that makes it usable
&lt;/h2&gt;

&lt;p&gt;Whatever the source shape, everything normalizes to one flat, snake_case contract:&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;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"oliveyoung"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"product_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;"GA230518746"&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;"SKIN1004 Madagascar Centella Hyalu-Cica Water-Fit Sun Serum"&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;"SKIN1004"&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_usd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;28.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;"sale_price_usd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;22.4&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.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"review_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9958&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rank"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;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;"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://global.oliveyoung.com/product/detail?prdtNo=GA230518746"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scraped_at"&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-07-07T09:35:33+09:00"&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;Consistent field names and KST timestamps across all three sources mean you can dump them into the same table and diff prices over time without writing per-site glue.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you'd rather not maintain any of this
&lt;/h2&gt;

&lt;p&gt;I packaged all three as ready-to-run scrapers on the Apify Store — they handle the proxying, the fallbacks, and the JSON normalization, and they're priced per result so you only pay for what you pull:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Olive Young&lt;/strong&gt; (K-beauty, USD prices): &lt;a href="https://apify.com/kdatafactory/oliveyoung-scraper" rel="noopener noreferrer"&gt;https://apify.com/kdatafactory/oliveyoung-scraper&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Musinsa&lt;/strong&gt; (K-fashion): &lt;a href="https://apify.com/kdatafactory/musinsa-scraper" rel="noopener noreferrer"&gt;https://apify.com/kdatafactory/musinsa-scraper&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Naver Place&lt;/strong&gt; (businesses + reviews): &lt;a href="https://apify.com/kdatafactory/naver-place-scraper" rel="noopener noreferrer"&gt;https://apify.com/kdatafactory/naver-place-scraper&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But honestly, the endpoints above are the whole trick — if you just need a one-off pull, the network tab will get you most of the way. Happy to answer questions on any of the three in the comments.&lt;/p&gt;

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