justin․searls․co

Exporting your Tabelog 行ったお店 history

This is going to be a niche one, but maybe somebody will Google for this someday.

This morning, I figured out a relatively low-effort way to export my visited restaurants (行ったお店) in Tabelog and then decorate them with latitude and longitude as well as translations of each restaurant's name and summary.

There are basically three steps:

  1. Gather each page of your visited restaurants using JavaScript in the console
  2. Export them to a JSON file
  3. In a Ruby script, update the JSON for each restaurant, adding:
    1. Latitude and longitude
    2. English translations of its name and summary

Gathering the restaurants in JavaScript

I tried futzing with using a browser automation tool for this, but I kept running into issues with how Tabelog's OAuth works, so just pasted this command twenty times in the development console, once for each page (aggregating all my visited places into a localStorage property):

let summaries = JSON.parse(localStorage.getItem('tabelogSummaries') || '[]')
let mostRecentDate = null
summaries = summaries.concat(Array.from(document.querySelectorAll('.rvw-item--simple')).map(node => {
  const date = node.querySelector('.p-preview-visit__visited-date')?.innerText.replace('訪問', '').replaceAll('/','-')
  if (date) mostRecentDate = date

  return {
    title: node.querySelector('.simple-rvw__rst-name-target').innerText,
    subtitle: node.querySelector('.simple-rvw__area-catg').innerText,
    mapLink: node.querySelector('.simple-rvw__rst-name-target').href,
    mapLinkType: 'tabelog',
    score: node.querySelector('.p-preview-visit--score .c-rating-v2__val')?.innerText,
    date: mostRecentDate,
    lat: null,
    lng: null
  }
}))
localStorage.setItem('tabelogSummaries', JSON.stringify(summaries))
console.log(JSON.stringify(summaries))

After running this on the final page of my visited restaurants list, I copied the last log output and saved it as tabelog_src.json

Enhancing the JSON with Ruby

Next, I ran this Ruby script to create tabelog_dest.json:

require "uri"
require "cgi"
require "nokogiri"
require "net/http"
require "json"

items = JSON.load_file("tabelog_src.json", symbolize_names: true)

# item shape:
# {
#   "placeTitle": "やきとりスタンド 京急川崎店",
#   "placeSubtitle": "京急川崎、川崎/居酒屋、焼き鳥、ラーメン",
#   "mapLink": "https://tabelog.com/kanagawa/A1405/A140501/14063800/",
#   "mapLinkType": "tabelog",
#   "score": "3.4",
#   "date": "2023-05-22",
#   "lat": null,
#   "lng": null
# }
items.map.with_index do |item, i|
  puts "Processing item #{i + 1}: #{item[:placeTitle]}..."

  # Load the detail page and extract the map image URL so we can nab its lat/lng
  url = URI.parse(item[:mapLink])
  response = Net::HTTP.get(url)
  document = Nokogiri::HTML(response)
  map_url = URI.parse(document.css(".rstinfo-table__map-image").first["data-original"])
  item[:lat], item[:lng] = CGI.parse(map_url.query)["center"].first.split(",")

  # brew install translate-shell to get the `trans` command
  item[:placeTitleAlt] = `trans -b -s ja #{item[:placeTitle].inspect}`.chomp
  item[:placeSubtitleAlt] = `trans -b -s ja #{item[:placeSubtitle].inspect}`.chomp
end

# Output item shape:
# {
#   "placeTitle": "やきとりスタンド 京急川崎店",
#   "placeSubtitle": "京急川崎、川崎/居酒屋、焼き鳥、ラーメン",
#   "mapLink": "https://tabelog.com/kanagawa/A1405/A140501/14063800/",
#   "mapLinkType": "tabelog",
#   "score": "3.4",
#   "date": "2023-05-22",
#   "lat": "35.532667702754814",
#   "lng": "139.69926311341322",
#   "placeTitleAlt": "Yakitori stand Keikyu Kawasaki store",
#   "placeSubtitleAlt": "Keikyu Kawasaki, Kawasaki/Izakaya, Yakitori, Ramen"
# },

File.write("tabelog_dest.json", JSON.pretty_generate(items))

This is what the script does:

  1. Read the JSON file
  2. Loop through each item to visit its detail page
  3. Extract the restaurant's coordinates from a Google Maps embed URL
  4. Shell out to translate-shell to translate all the names and summaries
  5. Dump the updated JSON

Stay tuned

Of course, my purpose in doing all this is to seed my new 📍 Spots section of this here web site with some of my favorite restaurants in Japan. I always find little scraping projects like this to be a fun puzzle to solve, so I thought I'd share this one's "solution" with you all, especially if anyone else is looking to make a backup of their own reviews. 💜


Got a taste for fresh, hot takes?

Then you're in luck, because you can subscribe to this site via RSS or Mastodon! And if that ain't enough, then sign up for my newsletter and I'll send you a usually-pretty-good essay once a month. I also have a solo podcast, because of course I do.