CoderCastrov logo
CoderCastrov
Парсинг веб-страниц

Как мы провели обратную разработку пагинации Google Maps

Как мы провели обратную разработку пагинации Google Maps
просмотров
3 мин чтение
#Парсинг веб-страниц

В этой истории вы увидите процесс декодирования параметров 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].

Параметр psi в window.APP_OPTIONS на Google Maps

Еще одна переменная - это список фильтров, но мы не знаем, как их разобрать.

Список фильтров в URL-адресах Google Maps для пагинации

Мы поняли, что мы можем сделать первый запрос к 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.

The initial formula for zoom = f(alt, lat) The simplified formula in Wolfram Alpha. Pretty nice, huh?

После нескольких часов чтения учебников по математике для средней школы мы обратили формулу.

z = f(alt, lat); alt = f(z, lat); and my reflection on the whiteboard

Код для генерации параметра pb

Параметр pb для пагинации Google Maps является функцией ll из URL и offset.

ll может содержать отрицательные и положительные значения широты, долготы и масштаба. Мы решили извлечь эти параметры с помощью регулярных выражений.

Преобразование параметра URL ll в pb для конкретного смещения результатов (start) на Google Maps выглядит следующим образом.

Собираем все вместе

Разбор пагинированных данных

У нас уже есть API для парсинга Google Maps, который извлекает данные из встроенного JavaScript в HTML. Милош Дюрдевич переработал его для поддержки извлечения из встроенного JS в HTML и из ответов пагинации. Что мы можем сказать здесь, наш парсер получает данные из глубоко вложенных массивов и объектов.


Возможно извлечение данных из сложных одностраничных приложений без автоматизации браузера. Для нас веселее понимать, как работает скрапируемый веб-сайт, вместо настройки времени ожидания в вызовах функций _waitFor_. Это также работает быстрее и проще в поддержке. Если это то, что вас вдохновляет, мы будем рады, если вы присоединитесь к нам.