<?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: Seok June Park</title>
    <description>The latest articles on DEV Community by Seok June Park (@seok_junepark_e7fa29e15f).</description>
    <link>https://dev.to/seok_junepark_e7fa29e15f</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%2F8138f0d8-4050-48a7-b88d-f0c4809a615e.png</url>
      <title>DEV Community: Seok June Park</title>
      <link>https://dev.to/seok_junepark_e7fa29e15f</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/seok_junepark_e7fa29e15f"/>
    <language>en</language>
    <item>
      <title>How I track K-beauty prices for dropshipping without writing a scraper</title>
      <dc:creator>Seok June Park</dc:creator>
      <pubDate>Thu, 09 Jul 2026 02:54:15 +0000</pubDate>
      <link>https://dev.to/seok_junepark_e7fa29e15f/how-i-track-k-beauty-prices-for-dropshipping-without-writing-a-scraper-2e9e</link>
      <guid>https://dev.to/seok_junepark_e7fa29e15f/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>Seok June Park</dc:creator>
      <pubDate>Wed, 08 Jul 2026 23:48:53 +0000</pubDate>
      <link>https://dev.to/seok_junepark_e7fa29e15f/scraping-koreas-biggest-stores-the-hidden-json-apis-behind-olive-young-musinsa-naver-4h2g</link>
      <guid>https://dev.to/seok_junepark_e7fa29e15f/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>
