Как мы провели обратную разработку пагинации Google Maps
В этой истории вы увидите процесс декодирования параметров URL для пагинации на Google Maps. Это включало деобфускацию скомпилированного JavaScript с использованием Closure, обратную разработку структур данных Protobuf и немного математики. Мы пытались декодировать параметры URL сами, используя pbtk, и пытались передать эту работу сторонним разработчикам. В конце концов, мы смогли декодировать параметры URL после нескольких сессий парного программирования.
Как работает пагинация Google Maps
Мы можем получить ссылку на следующую страницу только нажав кнопку "следующая страница".
Ссылки выглядят так.
Страница 1
[https://www.google.com/search?tbm=map&authuser=0&hl=en&gl=us&pb=!4m8!1m3!1d24182.00605141337!2d-74.0083012!3d40.7455096!3m2!1i1024!2i768!4f13.1!7i20!8i20!10b1!12m18!2m3!5m1!6e2!20e3!6m11!4b1!23b1!26i1!27i1!41i2!45b1!63m0!67b1!73m0!74i150000!89b1!10b1!16b1!19m4!2m3!1i360!2i120!4i8!20m57!2m2!1i203!2i100!3m2!2i4!5b1!6m6!1m2!1i86!2i86!1m2!1i408!2i240!7m42!1m3!1e1!2b0!3e3!1m3!1e2!2b1!3e2!1m3!1e2!2b0!3e3!1m3!1e3!2b0!3e3!1m3!1e8!2b0!3e3!1m3!1e3!2b1!3e2!1m3!1e9!2b1!3e2!1m3!1e10!2b0!3e3!1m3!1e10!2b1!3e2!1m3!1e10!2b0!3e4!2b1!4b1!9b0!22m3!1sjWLeXbmnHIXt-gTm2ouwDg%3A23!2zMWk6Mix0OjEyNjk2LGU6MSxwOmpXTGVYYm1uSElYdC1nVG0yb3V3RGc6MjM!7e81!24m40!1m12!13m6!2b1!3b1!4b1!6i1!8b1!9b1!18m4!3b1!4b1!5b1!6b1!2b1!5m5!2b1!3b1!5b1!6b1!7b1!10m1!8e3!14m1!3b1!17b1!20m2!1e3!1e6!24b1!25b1!26b1!30m1!2b1!36b1!43b1!52b1!55b1!56m2!1b1!3b1!26m4!2m3!1i80!2i92!4i8!30m28!1m6!1m2!1i0!2i0!2m2!1i458!2i768!1m6!1m2!1i974!2i0!2m2!1i1024!2i768!1m6!1m2!1i0!2i0!2m2!1i1024!2i20!1m6!1m2!1i0!2i748!2m2!1i1024!2i768!34m9!3b1!4b1!6b1!8m2!1b1!3b1!9b1!12b1!14b1!37m1!1e81!42b1!46m1!1e9!47m0!49m1!3b1!50m40!1m39!2m7!1u3!4sOpen+now!5e1!9s0ahUKEwj35K6aqYrmAhWFtp4KHWbtAuYQ_KkBCIMHKBc!10m2!3m1!1e1!2m7!1u2!4sTop+rated!5e1!9s0ahUKEwj35K6aqYrmAhWFtp4KHWbtAuYQ_KkBCIQHKBg!10m2!2m1!1e1!2m7!1u1!4sCheap!5e1!9s0ahUKEwj35K6aqYrmAhWFtp4KHWbtAuYQ_KkBCIUHKBk!10m2!1m1!1e1!2m7!1u1!4sUpscale!5e1!9s0ahUKEwj35K6aqYrmAhWFtp4KHWbtAuYQ_KkBCIYHKBo!10m2!1m1!1e2!3m1!1u2!3m1!1u1!3m1!1u3!4BIAE!59BQ2dBd0Fn&q=Coffee&tch=1&ech=1&psi=jWLeXbmnHIXt-gTm2ouwDg.1574855312836.1](https://www.google.com/search?tbm=map&authuser=0&hl=en&gl=us&pb=%214m8%211m3%211d24182.00605141337%212d-74.0083012%213d40.7455096%213m2%211i1024%212i768%214f13.1%217i20%218i20%2110b1%2112m18%212m3%215m1%216e2%2120e3%216m11%214b1%2123b1%2126i1%2127i1%2141i2%2145b1%2163m0%2167b1%2173m0%2174i150000%2189b1%2110b1%2116b1%2119m4%212m3%211i360%212i120%214i8%2120m57%212m2%211i203%212i100%213m2%212i4%215b1%216m6%211m2%211i86%212i86%211m2%211i408%212i240%217m42%211m3%211e1%212b0%213e3%211m3%211e2%212b1%213e2%211m3%211e2%212b0%213e3%211m3%211e3%212b0%213e3%211m3%211e8%212b0%213e3%211m3%211e3%212b1%213e2%211m3%211e9%212b1%213e2%211m3%211e10%212b0%213e3%211m3%211e10%212b1%213e2%211m3%211e10%212b0%213e4%212b1%214b1%219b0%2122m3%211sjWLeXbmnHIXt-gTm2ouwDg%3A23%212zMWk6Mix0OjEyNjk2LGU6MSxwOmpXTGVYYm1uSElYdC1nVG0yb3V3RGc6MjM%217e81%2124m40%211m12%2113m6%212b1%213b1%214b1%216i1%218b1%219b1%2118m4%213b1%214b1%215b1%216b1%212b1%215m5%212b1%213b1%215b1%216b1%217b1%2110m1%218e3%2114m1%213b1%2117b1%2120m2%211e3%211e6%2124b1%2125b1%2126b1%2130m1%212b1%2136b1%2143b1%2152b1%2155b1%2156m2%211b1%213b1%2126m4%212m3%211i80%212i92%214i8%2130m28%211m6%211m2%211i0%212i0%212m2%211i458%212i768%211m6%211m2%211i974%212i0%212m2%211i1024%212i768%211m6%211m2%211i0%212i0%212m2%211i1024%212i20%211m6%211m2%211i0%212i748%212m2%211i1024%212i768%2134m9%213b1%214b1%216b1%218m2%211b1%213b1%219b1%2112b1%2114b1%2137m1%211e81%2142b1%2146m1%211e9%2147m0%2149m1%213b1%2150m40%211m39%212m7%211u3%214sOpen+now%215e1%219s0ahUKEwj35K6aqYrmAhWFtp4KHWbtAuYQ_KkBCIMHKBc%2110m2%213m1%211e1%212m7%211u2%214sTop+rated%215e1%219s0ahUKEwj35K6aqYrmAhWFtp4KHWbtAuYQ_KkBCIQHKBg%2110m2%212m1%211e1%212m7%211u1%214sCheap%215e1%219s0ahUKEwj35K6aqYrmAhWFtp4KHWbtAuYQ_KkBCIUHKBk%2110m2%211m1%211e1%212m7%211u1%214sUpscale%215e1%219s0ahUKEwj35K6aqYrmAhWFtp4KHWbtAuYQ_KkBCIYHKBo%2110m2%211m1%211e2%213m1%211u2%213m1%211u1%213m1%211u3%214BIAE%2159BQ2dBd0Fn&q=Coffee&tch=1&ech=1&psi=jWLeXbmnHIXt-gTm2ouwDg.1574855312836.1)
Страница 2
[https://www.google.com/search?tbm=map&authuser=0&hl=en&gl=us&pb=!4m8!1m3!1d24182.00605141337!2d-74.0083012!3d40.7455096!3m2!1i1024!2i768!4f13.1!7i20!8i20!10b1!12m18!2m3!5m1!6e2!20e3!6m11!4b1!23b1!26i1!27i1!41i2!45b1!63m0!67b1!73m0!74i150000!89b1!10b1!16b1!19m4!2m3!1i360!2i120!4i8!20m57!2m2!1i203!2i100!3m2!2i4!5b1!6m6!1m2!1i86!2i86!1m2!1i408!2i240!7m42!1m3!1e1!2b0!3e3!1m3!1e2!2b1!3e2!1m3!1e2!2b0!3e3!1m3!1e3!2b0!3e3!1m3!1e8!2b0!3e3!1m3!1e3!2b1!3e2!1m3!1e9!2b1!3e2!1m3!1e10!2b0!3e3!1m3!1e10!2b1!3e2!1m3!1e10!2b0!3e4!2b1!4b1!9b0!22m3!1sjWLeXbmnHIXt-gTm2ouwDg%3A78!2zMWk6Mix0OjEyNjk2LGU6MSxwOmpXTGVYYm1uSElYdC1nVG0yb3V3RGc6Nzg!7e81!24m40!1m12!13m6!2b1!3b1!4b1!6i1!8b1!9b1!18m4!3b1!4b1!5b1!6b1!2b1!5m5!2b1!3b1!5b1!6b1!7b1!10m1!8e3!14m1!3b1!17b1!20m2!1e3!1e6!24b1!25b1!26b1!30m1!2b1!36b1!43b1!52b1!55b1!56m2!1b1!3b1!26m4!2m3!1i80!2i92!4i8!30m28!1m6!1m2!1i0!2i0!2m2!1i458!2i768!1m6!1m2!1i974!2i0!2m2!1i1024!2i768!1m6!1m2!1i0!2i0!2m2!1i1024!2i20!1m6!1m2!1i0!2i748!2m2!1i1024!2i768!34m9!3b1!4b1!6b1!8m2!1b1!3b1!9b1!12b1!14b1!37m1!1e81!42b1!46m1!1e9!47m0!49m1!3b1!50m40!1m39!2m7!1u3!4sOpen+now!5e1!9s0ahUKEwj35K6aqYrmAhWFtp4KHWbtAuYQ_KkBCIMHKBc!10m2!3m1!1e1!2m7!1u2!4sTop+rated!5e1!9s0ahUKEwj35K6aqYrmAhWFtp4KHWbtAuYQ_KkBCIQHKBg!10m2!2m1!1e1!2m7!1u1!4sCheap!5e1!9s0ahUKEwj35K6aqYrmAhWFtp4KHWbtAuYQ_KkBCIUHKBk!10m2!1m1!1e1!2m7!1u1!4sUpscale!5e1!9s0ahUKEwj35K6aqYrmAhWFtp4KHWbtAuYQ_KkBCIYHKBo!10m2!1m1!1e2!3m1!1u2!3m1!1u1!3m1!1u3!4BIAE!59BQ2dBd0Fn&q=Coffee&tch=1&ech=2&psi=jWLeXbmnHIXt-gTm2ouwDg.1574855312836.1](https://www.google.com/search?tbm=map&authuser=0&hl=en&gl=us&pb=%214m8%211m3%211d24182.00605141337%212d-74.0083012%213d40.7455096%213m2%211i1024%212i768%214f13.1%217i20%218i20%2110b1%2112m18%212m3%215m1%216e2%2120e3%216m11%214b1%2123b1%2126i1%2127i1%2141i2%2145b1%2163m0%2167b1%2173m0%2174i150000%2189b1%2110b1%2116b1%2119m4%212m3%211i360%212i120%214i8%2120m57%212m2%211i203%212i100%213m2%212i4%215b1%216m6%211m2%211i86%212i86%211m2%211i408%212i240%217m42%211m3%211e1%212b0%213e3%211m3%211e2%212b1%213e2%211m3%211e2%212b0%213e3%211m3%211e3%212b0%213e3%211m3%211e8%212b0%213e3%211m3%211e3%212b1%213e2%211m3%211e9%212b1%213e2%211m3%211e10%212b0%213e3%211m3%211e10%212b1%213e2%211m3%211e10%212b0%213e4%212b1%214b1%219b0%2122m3%211sjWLeXbmnHIXt-gTm2ouwDg%3A78%212zMWk6Mix0OjEyNjk2LGU6MSxwOmpXTGVYYm1uSElYdC1nVG0yb3V3RGc6Nzg%217e81%2124m40%211m12%2113m6%212b1%213b1%214b1%216i1%218b1%219b1%2118m4%213b1%214b1%215b1%216b1%212b1%215m5%212b1%213b1%215b1%216b1%217b1%2110m1%218e3%2114m1%213b1%2117b1%2120m2%211e3%211e6%2124b1%2125b1%2126b1%2130m1%212b1%2136b1%2143b1%2152b1%2155b1%2156m2%211b1%213b1%2126m4%212m3%211i80%212i92%214i8%2130m28%211m6%211m2%211i0%212i0%212m2%211i458%212i768%211m6%211m2%211i974%212i0%212m2%211i1024%212i768%211m6%211m2%211i0%212i0%212m2%211i1024%212i20%211m6%211m2%211i0%212i748%212m2%211i1024%212i768%2134m9%213b1%214b1%216b1%218m2%211b1%213b1%219b1%2112b1%2114b1%2137m1%211e81%2142b1%2146m1%211e9%2147m0%2149m1%213b1%2150m40%211m39%212m7%211u3%214sOpen+now%215e1%219s0ahUKEwj35K6aqYrmAhWFtp4KHWbtAuYQ_KkBCIMHKBc%2110m2%213m1%211e1%212m7%211u2%214sTop+rated%215e1%219s0ahUKEwj35K6aqYrmAhWFtp4KHWbtAuYQ_KkBCIQHKBg%2110m2%212m1%211e1%212m7%211u1%214sCheap%215e1%219s0ahUKEwj35K6aqYrmAhWFtp4KHWbtAuYQ_KkBCIUHKBk%2110m2%211m1%211e1%212m7%211u1%214sUpscale%215e1%219s0ahUKEwj35K6aqYrmAhWFtp4KHWbtAuYQ_KkBCIYHKBo%2110m2%211m1%211e2%213m1%211u2%213m1%211u1%213m1%211u3%214BIAE%2159BQ2dBd0Fn&q=Coffee&tch=1&ech=2&psi=jWLeXbmnHIXt-gTm2ouwDg.1574855312836.1)
Они ведут к файлу f.txt
, который содержит результаты следующей страницы.
План
Декодирование параметров URL для пагинации Google Maps
URL-адреса Google Maps содержат параметр pb
, который содержит строково-кодированный Protobuf. Формат такой же, как и для параметра data
в URL-адресе браузера на Google Maps. Он содержит значения, разделенные символом !
. На StackOverflow есть несколько ответов, гистов на GitHub, некоторые блог посты о декодировании, и даже своего рода официальное руководство по обратной разработке protobuf, но ни одно из этого не затрагивает пагинацию.
Мы попытались использовать [pbtk](https://github.com/marin-m/pbtk)
, но он не смог извлечь структуры и завершился с ошибкой. Несколько попыток прочитать красиво отформатированный обфусцированный JavaScript не сработали.
После совместной работы с Милошем Дюрдевичем, мы обнаружили большую часть переменных в URI пагинации Google Maps: latitude
(широта), longitude
(долгота), altitude_in_feets
(высота в футах), pagination_offset
(смещение пагинации), некий параметр, который равен psi, но я не знаю его значения
. psi
меняется после каждой перезагрузки страницы и находится в window.APP_OPTIONS[11]
.
Еще одна переменная - это список фильтров, но мы не знаем, как их разобрать.
Мы поняли, что мы можем сделать первый запрос к Google Maps, извлечь переменные и сконструировать URI пагинации. Как и для нашего API для парсинга YouTube.
Быстрая проверка подтвердила, что алгоритм работает.
Мы не хотели добавлять новые параметры (long
, lat
, alt
) в наше API специально для пагинации, поэтому мы попытались найти способы преобразовать alt
из zoom. Но эти формулы не соответствуют высоте в URL-адресах пагинации, которые использует Google Maps.
Кроме того, высота зависит от количества пикселей на дюйм, которое различается на разных устройствах, и Google масштабирует карту для отображения всех мест на карте. (На самом деле это было несущественно).
Милош Дюрдевич объединил несколько формул из JS-кода в Google Maps в формулу для преобразования высоты в зум для заданной широты.
zoom(24182.00605141337, 40.7455096);
>>> 14
Последняя недостающая часть - обратная формула.
Преобразование зума в высоту
Мы упростили формулу зума в Wolfram Alpha.
После нескольких часов чтения учебников по математике для средней школы мы обратили формулу.
Код для генерации параметра pb
Параметр pb
для пагинации Google Maps является функцией ll
из URL и offset
.
ll
может содержать отрицательные и положительные значения широты
, долготы
и масштаба
. Мы решили извлечь эти параметры с помощью регулярных выражений.
Преобразование параметра URL ll
в pb
для конкретного смещения результатов (start
) на Google Maps выглядит следующим образом.
Собираем все вместе
Разбор пагинированных данных
У нас уже есть API для парсинга Google Maps, который извлекает данные из встроенного JavaScript в HTML. Милош Дюрдевич переработал его для поддержки извлечения из встроенного JS в HTML и из ответов пагинации. Что мы можем сказать здесь, наш парсер получает данные из глубоко вложенных массивов и объектов.
Возможно извлечение данных из сложных одностраничных приложений без автоматизации браузера. Для нас веселее понимать, как работает скрапируемый веб-сайт, вместо настройки времени ожидания в вызовах функций _waitFor_
. Это также работает быстрее и проще в поддержке. Если это то, что вас вдохновляет, мы будем рады, если вы присоединитесь к нам.