[Из песочницы] Как я боролся с воровством… с помощью php

2pm5qpfuvq5rm_bigp5jiekyy0i.png
Когда мы платим ежедневно за услуги — это покупка услуг.
Когда мы платим ежедневно за ничего (порой даже не подозревая об этом) — это воровство.

Добрый день, читатели Хабра!

С чего всё началось


Захотел я чтоб воровства стало меньше, и давай с ним бороться! Но вручную это было очень утомительно, долго и малоэффективно, тогда и пришла мысль как-то это дело автоматизировать.

О котором из «воровств» я? О том, где мы, гуляя по интернету, нажимаем на кнопочку «смотреть видео»,   грузится какая-то страница, видео почему-то не проигрывается, мы уходим и гуляем дальше, а на самом деле мы «добровольно» подключили себе услугу получать что-то, что никто никогда не видел за символическую плату 30 рублей в день со счёта своего мобильного. У людей это называется wap-click или мобильные подписки, а сотовые операторы придумывают разнообразные красивые названия. Ещё бы, не включать же в список услуг «воровство по видеокнопке».
Вот здесь чуть подробнее. А здесь история о хорошем способе «заработать».

Описанных случаев не совсем добровольных подписок много, этот, например. Неописанных — намного больше.

Борцы тоже есть:


Что и зачем было автоматизировано


Поиск и блокирование объявлений в панели издателя Google AdSense.
Цель — повысить эффективность блокирования и освободить время, которое тратится на чистку вручную.

Суть проблемы и имеющиеся решения
Долгие годы (первое упоминание о подобном, что я нашёл было летом 2014-го) издатели вручную отлавливали потоки «смертей Якубовича», «каменных стояков», «смотреть видео смотреть, жми смотреть» и прочей нечисти (начало, продолжение), сей процесс почти никак не автоматизировался1 и это казалось практически невозможным.

1 Есть (по крайней мере когда-то было) два решения, но у них довольно серьёзные требования, которые не каждый может себе позволить.
Эти самые решения:

  1. AdSense Cleaner. Требуется много доп. ПО.
  2. AdsAutomation. Сценарий для управления браузером Google Chrome (как я понял, на ZennoPoster). Необходим отдельный ПК. И в данный момент с GitHub проект удалён.


Если делать ПО, которое заменит человека блокирующего объявления, то оно должен быть сделано с учётом ряда требований:

  • должно работать на том «железе» и ПО, которое есть практически у всех сайтовладельцев;
  • не требовать дополнительного ПО и изменения настроек имеющегося;
  • простота в установке и настройке, чтобы обычный пользователь смог поставить.

.

В общем, на php (с cURL) будет что надо. Закинуть можно прямо на свой сайт и работать без дополнительных компов и прочих сложностей.

И одно уточнение к требованиям.

Так как решение подразумевалось автоматизированным на php, следовательно, запуск через cron, то хранение пользовательских настроек и временных данных должно быть на диске (не в cookie). В Cookie-файлах будет хранится только ключ для доступа к панели управления. Для избранных, кто не имеет возможности настроить cron, но может на ПК/планшете/смартфоне держать одну вкладку открытой будет добавлена возможность периодического запуска по таймеру на Javascript.


Что предвещало начало или Google API


И для AdSense есть API, как-то краем глаза видел и не углублялся. А сейчас — самое время вникнуть. Возможностей много, но оказалось, что ни здесь, ну тут ничего не описано про API для ЦПО. Хочешь смотреть объявления, которые на сайте крутятся, пожалуйста — вручную.

Начало


Интерфейс Google AdSense построен на Javascript, там с виду всё красиво и довольно сложно с точки зрения устройства.

Первым делом заглянул в инструменты разработчика Google Chrome на вкладку «Network», чтобы «подслушать» как этот навороченный интерфейс общается с сервером. Запросов там уйма, самые интересные для меня были в разделе «XHR and Fetch», там-то я и нашёл, то что выглядело вполне разгадываемым, если хорошо подумать. Например, один из post-запросов:

Передаваемая строка.
{"method":"searchArcApprovals","params":"{\"1\":\"ca-pub-6928690776790362\",\"2\":{\"1\":0,\"2\":1,\"3\":0,\"4\":{\"1\":{\"1\":\"AClZvXKL6S3HChRty5YBa81BLWDBQkb3FYDsifZ9V/mBTKbOGlj3gMWVpzTtXggA1880Le9NyVZIicNm/4pz724e/MO8fyLfjOReF205cyjLV9C8OCCeKe7VvZHyvyKpXh8x9smTQ0n8qIIqzuIXle5UK0hD4VBkZDvy//qoSPRCr94UtWYqqi//Rot22LJ2JFNjWEGb4n1YQbAw0cKWPR3LAugPBajInWXEFGWJRTnmY2TkI5VzUzIkcXpJ/bkajn3c8GnecCfFNvNhGLS10VXdRwiykngG3xfoMTRhQOR5GXbm4kwdIhzQUM/d6xP0Xda3FOIZGGk9bymneg+9oDY+rMFiRfDFCb66g50t9J9r++oHXjek09Ci1rqC7LOw2pvkqp3hjG6RyVmsiT/eWGq+OsfjE7CgRk43QIRMSa+jlZBQhARUPlpUXzyZyoTiIPTRZ5ND/4MnIMqaUWSRoDGffiE/XkHJPEkNZtLX2XR5gZ3x5/K+ejU/fqxfZIjI6A3kueJybNA46wSLbmflhDCGDJEE2aeYemLFGqNzFG43B80LzU3yuwgZhrLu/jaMvBJozi0nq+gXEz6r+8tic4fvsQ9lWDA+IXzXw6MKzamgfWV0ORGDW0+966KIY6IkjtIlNRKGyp3pSAd2Po+br4Dl4WNwSkMdmuV60wOrkb5BpnKZKIhDtpjWF7q6ly3FFhwo8Ktdq5ddVJ8ijJ9Y9tQhs2O0idA9N0yV86khV1IQ72OgbMv15qAswnbqF9WCo3qpfJNjJqMCHBRTohPCxhRp0cWz2thszZTmDDADPxU46sclnurd/JxHFO7lJZVdrsFB4vdLIx9kObV3bP1gOpU66kdcmom2tiedknugj7s0jLcgf1EfXnp+SUUAQyoqwS+kdhhQtGqSXgI2TopsuaLVzj+EtAuPwWeLvtI9CFPSe4o2x+gjCRPl8wVvWKV5FIrZavUVOAHZIL4nKyJjHxZi3jPfVnAia/hq1gW6XKoCg1eWGg/cAWZY4mZYQ6W4XnC0MY0uMC6fhPQdXnIS5iLZNhan80jbr/leBr4fO22+tXc6oZpZsDkXd0r3ilBJFPS2I/zAhotuzZgNA+nF2N86pyiSrdeEYFDhKWKadcKAVc3BMxxlrqZYcAXnlus9GW7R9F/ImXQ/fjRfSjVRUaJuQ0EnFejNAwdGcS6STYMa1G0wnNMAKcZ52xcHgil1SZ6N9BQ7A27z6eViOxw0LHBqNJIRZwQml2KjPd5b00D9XvohDr6jBqYXLGS/HMVvpGDJZLDI2LRlmkqBqx7YEgDZqvspeoMLHIJP22SkQDnaJtsOLGVBSi20ZD5nRyjAgS6MmcgFCvfJVWjCIL1RPHqmUU90eK4WXve0ayH9cJnpbtWrkXYCibhVPCMmYowMROw7rI4bPir0\"}}}}","xsrf":"ABOvogKvrE9fIqAKh0w02RIsB4OJ4hsB_g:1535467885347"}


В запросе сразу виден идентификатор издателя, под вторым пунктом набор параметров, суть которых можно выяснить экспериментальным путём и жетон XSRF.

А в ответ получает подробную информацию об объявлении, но не всю и без самого объявления (здесь и далее картинки, вытянутые в base64, подрезал).

Простыня на несколько страниц.
{"result":{"1":[{"1":0,"3":0,"4":{"1":"AClZvXJ2t4wiEZ/VZ0i54m0Qtqpi2DTqkI1kaPMTRi4LnsQn0iR5K1xBlFpS1xmJV7ko4a6qx5RcTkp7CzVjwoy5UDSWZ5jOCPLGRcoQdDt+wOk46bdr0yA\u003d"},"5":{"1":82,"2":0,"3":0,"4":"\u003cdiv id\u003d\"ad-parent-id-6A2DE3D206234468F53C743C0EEACD67A59E6C5B62C0371F770419826258CB1AD9591F60\"\u003e\u003c/div\u003e","5":"\u003cdiv id\u003d\"ad-parent-id-6A2DE3D206234468F53C743C0EEACD67A59E6C5B62C0371F770419826258CB1AD9591F60\"\u003e\u003c/div\u003e","6":"\u003cdiv\u003e\u041c\u043d\u043e\u0433\u043e\u0444\u043e\u0440\u043c\u0430\u0442\u043d\u044b\u0435\u003cspan id\u003d'multi-format-tooltip'\u003e\u003c/span\u003e\u003c/div\u003e\u003ca class\u003d'arc-url-link-ellipsis' target\u003d'_blank' href\u003d'https://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/' title\u003d'https://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/'\u003ehttps://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/\u003c/a\u003e","7":"\u003cdiv class\u003d'arc-one-by-one-legend'\u003e\u0422\u0438\u043f \u043e\u0431\u044a\u044f\u0432\u043b\u0435\u043d\u0438\u044f\u003c/div\u003e\u003cdiv class\u003d'arc-one-by-one-data'\u003e\u041c\u043d\u043e\u0433\u043e\u0444\u043e\u0440\u043c\u0430\u0442\u043d\u044b\u0435\u003cspan id\u003d'multi-format-tooltip'\u003e\u003c/span\u003e\u003c/div\u003e\u003cdiv class\u003d'arc-one-by-one-legend'\u003e\u0426\u0435\u043b\u0435\u0432\u043e\u0439 URL\u003c/div\u003e\u003cdiv class\u003d'arc-one-by-one-data'\u003e\u003ca class\u003d'arc-url-link-ellipsis' target\u003d'_blank' href\u003d'https://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/' title\u003d'https://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/'\u003ehttps://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/\u003c/a\u003e\u003c/div\u003e\u003cdiv class\u003d'arc-one-by-one-legend'\u003e\u0414\u043e\u043c\u0435\u043d\u044b \u0438\u0437\u0434\u0430\u0442\u0435\u043b\u0435\u0439\u003c/div\u003e\u003cdiv class\u003d'arc-one-by-one-data'\u003e4aynikam.ru\u003c/div\u003e\u003cdiv class\u003d'arc-one-by-one-data'\u003eandroidphone.su\u003c/div\u003e\u003cdiv class\u003d'arc-one-by-one-data'\u003eandroidphones.ru\u003c/div\u003e\u003cdiv class\u003d'arc-one-by-one-data'\u003efull-repair.com\u003c/div\u003e\u003cdiv class\u003d'arc-one-by-one-data'\u003ehowgadget.com\u003c/div\u003e\u003cdiv class\u003d'arc-one-by-one-legend'\u003e\u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0439 \u0440\u0435\u043a\u043b\u0430\u043c\u043e\u0434\u0430\u0442\u0435\u043b\u044c\u003cspan id\u003d'adx-advertiser-tooltip'\u003e\u003c/span\u003e\u003c/div\u003e\u003cdiv class\u003d'arc-one-by-one-data'\u003eDNS Shop\u003c/div\u003e","8":"\u003cdiv\u003e\u003cspan class\u003d'arc-impression-score high'\u003e\u0412\u042b\u0421\u041e\u041a\u041e\u0415\u003c/span\u003e \u0447\u0438\u0441\u043b\u043e \u043f\u043e\u043a\u0430\u0437\u043e\u0432\u003c/div\u003e","9":{"1":"\u003ca href\u003d\"https://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/\" target\u003d\"_blank\"\u003e\u003cimg onerror\u003d\"this.src\u003d'data:image/gif;base64,RA7'\" src\u003d\"https://www.google.com/webpagethumbnail?c\u003d58\u0026s\u003d400:400\u0026r\u003d4\u0026d\u003dhttps://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/\u0026a\u003dAIYkKU9ZGGjFTOWtm771MQwgDYxqtlBLCw\" border\u003d0 alt\u003d\"\"\u003e\u003c/a\u003e","2":"\u003ca href\u003d\"https://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/\" target\u003d\"_blank\"\u003e\u003cimg onerror\u003d\"this.src\u003d'data:image/gif;base64,R0AA7'\" src\u003d\"https://www.google.com/webpagethumbnail?c\u003d58\u0026s\u003d400:400\u0026r\u003d3\u0026d\u003dhttps://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/\u0026a\u003dAIYkKU_CQ2K6v5f11Nk1RXtc87FtmG2B1w\" border\u003d0 alt\u003d\"\"\u003e\u003c/a\u003e","3":"\u003ca href\u003d\"https://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/\" target\u003d\"_blank\"\u003e\u003cimg onerror\u003d\"this.src\u003d'data:image/gif;base64,R0lAA7'\" src\u003d\"https://www.google.com/webpagethumbnail?c\u003d58\u0026s\u003d400:400\u0026r\u003d6\u0026d\u003dhttps://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/\u0026a\u003dAIYkKU_My0a48LAsW-ZKpQX-ATXkMoPEVg\" border\u003d0 alt\u003d\"\"\u003e\u003c/a\u003e"},"10":"https://adwords-displayads.googleusercontent.com/da/b/preview.js?client\u003dasfe-arc-external-preview\u0026obfuscatedCustomerId\u003d5240877441\u0026creativeId\u003d288930210411\u0026htmlParentId\u003dad-parent-id-6A2DE3D206234468F53C743C0EEACD67A59E6C5B62C0371F770419826258CB1AD9591F60\u0026sig\u003dACiVB_yMUjLwDjRO2T-0VAaVuRPt8uLHGQ","13":"https://adwords-displayads.googleusercontent.com/da/b/preview.js?client\u003dasfe-arc-external-preview\u0026obfuscatedCustomerId\u003d5240877441\u0026creativeId\u003d288930210411\u0026htmlParentId\u003dad-parent-id-6A2DE3D206234468F53C743C0EEACD67A59E6C5B62C0371F770419826258CB1AD9591F60\u0026showVariations\u003dtrue\u0026sig\u003dACiVB_yMUjLwDjRO2T-0VAaVuRPt8uLHGQ","14":"https://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/","15":"","17":"","18":"DNS Shop","20":"adv-5594449542310820","21":["site1.ru","site2.com","site3.com","site4.ru"]},"6":{"5":"-6668648012302470727","7":["DNS"],"9":0},"7":1,"9":{"3":[{"1":{"1":"AClZvXLE9HJbFYq9TrAsXFgV4YkXsQt9lXp1xWjSB5aT5bFBpe4VNgo\u003d"},"2":"\u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442 \u0438 \u0442\u0435\u043b\u0435\u043a\u043e\u043c\u043c\u0443\u043d\u0438\u043a\u0430\u0446\u0438\u0438","3":"\u0422\u043e\u0432\u0430\u0440\u044b \u0438 \u0443\u0441\u043b\u0443\u0433\u0438, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0435 \u0441 \u0442\u0435\u043b\u0435\u043a\u043e\u043c\u043c\u0443\u043d\u0438\u043a\u0430\u0446\u0438\u044f\u043c\u0438, \u0432 \u0442\u043e\u043c \u0447\u0438\u0441\u043b\u0435 \u043a\u0430\u0431\u0435\u043b\u044c\u043d\u043e\u0435 \u0438 \u0441\u043f\u0443\u0442\u043d\u0438\u043a\u043e\u0432\u043e\u0435 \u043e\u0431\u0441\u043b\u0443\u0436\u0438\u0432\u0430\u043d\u0438\u0435 \u0438 \u0434\u043e\u0441\u0442\u0443\u043f \u0432 \u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442."},{"1":{"1":"AClZvXKrUJJ3kKBen2scP56BynOtGhf160i1F1LLmtBj3b/oh2dUFg8\u003d"},"2":"\u041c\u043e\u0431\u0438\u043b\u044c\u043d\u044b\u0435 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u044b","3":"\u041c\u043e\u0431\u0438\u043b\u044c\u043d\u044b\u0435 \u0438 \u0441\u043e\u0442\u043e\u0432\u044b\u0435 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u044b, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0441\u043e\u043f\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0430\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u0442\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u0445\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u0438\u0441\u0442\u0438\u043a\u0438 \u0438 \u0441\u0440\u0430\u0432\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0430\u043d\u0430\u043b\u0438\u0437 \u0442\u043e\u0432\u0430\u0440\u043e\u0432. \u0412 \u044d\u0442\u0443 \u043a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044e \u043d\u0435 \u0432\u0445\u043e\u0434\u044f\u0442 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u044b \u0434\u043b\u044f \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u044b\u0445 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u043e\u0432."},{"1":{"1":"AClZvXL4W+khZ4O9SJiu97cTbTs2+0Wecf1IVNju8ffd4ysIT9PJ7XY\u003d"},"2":"\u041c\u043e\u0431\u0438\u043b\u044c\u043d\u044b\u0435 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u044b \u0438 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u044b \u0434\u043b\u044f \u043d\u0438\u0445","3":"\u041c\u043e\u0431\u0438\u043b\u044c\u043d\u044b\u0435 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u044b, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0441\u043e\u043f\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u044b \u0438 \u0430\u043f\u043f\u0430\u0440\u0430\u0442\u043d\u043e\u0435 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0435\u043d\u0438\u0435, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u0447\u0435\u0445\u043b\u044b, \u043c\u043e\u043d\u043e\u043f\u043e\u0434\u044b \u0434\u043b\u044f \u0441\u0435\u043b\u0444\u0438, \u0437\u0430\u0449\u0438\u0442\u043d\u044b\u0435 \u044d\u043a\u0440\u0430\u043d\u044b \u0438 \u0437\u0430\u0440\u044f\u0434\u043d\u044b\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430."},{"1":{"1":"AClZvXLQ3gPoVwjQbokDpB3+nni4xURwH5+YlnwkqjYtUowjhiKvk8Q\u003d"},"2":"\u041f\u041a \u0438 \u0431\u044b\u0442\u043e\u0432\u0430\u044f \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u0438\u043a\u0430","3":"\u0422\u043e\u0432\u0430\u0440\u044b, \u0443\u0441\u043b\u0443\u0433\u0438 \u0438 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0435 \u0441 \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u0430\u043c\u0438 \u0438 \u0431\u044b\u0442\u043e\u0432\u043e\u0439 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u0438\u043a\u043e\u0439."},{"1":{"1":"AClZvXLKYOGgOROaa32IUxU15jP89AtTM4dV24WKS+daMhqJMTNmeSY\u003d"},"2":"\u0422\u0435\u043b\u0435\u0444\u043e\u043d\u0438\u044f","3":"\u0422\u043e\u0432\u0430\u0440\u044b, \u0443\u0441\u043b\u0443\u0433\u0438, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u043e\u043d\u043d\u044b\u0435 \u0438 \u0434\u0440\u0443\u0433\u0438\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u044b, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0435 \u0441 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0438\u0435\u0439 \u0438 \u0433\u043e\u043b\u043e\u0441\u043e\u0432\u043e\u0439 \u0441\u0432\u044f\u0437\u044c\u044e."}]},"10":{"1":"AClZvXLdGOShgJo+BM3apOUAFzQkE41z1/hiZhIY8eUlC7p7xXPm82P3dq7yXhbEI+tN/YHgdH4P"}}],"2":0.0,"3":"60609","4":1,"5":"","6":"ClD3Z2nP2P/////1/ff99fXV98nMyMrJz8rH9fHV883Hx8bMz83Oz8vOzv8A/v/+9f33/fX11ffJzMjKyc/Kx/Xx1fPNx8fGzM/Nzs/Lzs7//hABIWxUk293Pm+qOQAAAAAnMJaYSAFQAFoLCS8wxxaTatL1EAJgp7737gY\u003d","7":"3639","9":0},"xsrf":"ABOvogKaRsVZECZZJU-gDWrOqoP0CSqf7Q:1535467886413"}


После json_decode это выглядит примерно так:

Объект из json-строки (осторожно, 175 строк).

object(stdClass)#19 (2) {
  ["result"]=>
  object(stdClass)#18 (8) {
    ["1"]=>
    array(1) {
      [0]=>
      object(stdClass)#1 (8) {
        ["1"]=>
        int(0)
        ["3"]=>
        int(0)
        ["4"]=>
        object(stdClass)#2 (1) {
          ["1"]=>
          string(120) "AClZvXJ2t4wiEZ/VZ0i54m0Qtqpi2DTqkI1kaPMTRi4LnsQn0iR5K1xBlFpS1xmJV7ko4a6qx5RcTkp7CzVjwoy5UDSWZ5jOCPLGRcoQdDt+wOk46bdr0yA="
        }
        ["5"]=>
        object(stdClass)#3 (17) {
          ["1"]=>
          int(82)
          ["2"]=>
          int(0)
          ["3"]=>
          int(0)
          ["4"]=>
          string(102) "
" ["5"]=> string(102) "
" ["6"]=> string(355) "
Многоформатные
https://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/" ["7"]=> string(1066) "
Тип объявления
Многоформатные
Целевой URL
Домены издателей
4aynikam.ru
androidphone.su
androidphones.ru
full-repair.com
howgadget.com
Обнаруженный рекламодатель
DNS Shop
" ["8"]=> string(98) "
ВЫСОКОЕ число показов
" ["9"]=> object(stdClass)#4 (3) { ["1"]=> string(4191) "" ["2"]=> string(4191) "" ["3"]=> string(4191) "" } ["10"]=> string(291) "https://adwords-displayads.googleusercontent.com/da/b/preview.js?client=asfe-arc-external-preview&obfuscatedCustomerId=5240877441&creativeId=288930210411&htmlParentId=ad-parent-id-6A2DE3D206234468F53C743C0EEACD67A59E6C5B62C0371F770419826258CB1AD9591F60&sig=ACiVB_yMUjLwDjRO2T-0VAaVuRPt8uLHGQ" ["13"]=> string(311) "https://adwords-displayads.googleusercontent.com/da/b/preview.js?client=asfe-arc-external-preview&obfuscatedCustomerId=5240877441&creativeId=288930210411&htmlParentId=ad-parent-id-6A2DE3D206234468F53C743C0EEACD67A59E6C5B62C0371F770419826258CB1AD9591F60&showVariations=true&sig=ACiVB_yMUjLwDjRO2T-0VAaVuRPt8uLHGQ" ["14"]=> string(69) "https://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/" ["15"]=> string(0) "" ["17"]=> string(0) "" ["18"]=> string(8) "DNS Shop" ["20"]=> string(20) "adv-5594449542310820" ["21"]=> array(4) { [0]=> string(8) "site1.ru" [1]=> string(9) "site2.com" [2]=> string(9) "site3.com" [3]=> string(8) "site4.ru" } } ["6"]=> object(stdClass)#5 (3) { ["5"]=> string(20) "-6668648012302470727" ["7"]=> array(1) { [0]=> string(3) "DNS" } ["9"]=> int(0) } ["7"]=> int(1) ["9"]=> object(stdClass)#16 (1) { ["3"]=> array(5) { [0]=> object(stdClass)#7 (3) { ["1"]=> object(stdClass)#6 (1) { ["1"]=> string(56) "AClZvXLE9HJbFYq9TrAsXFgV4YkXsQt9lXp1xWjSB5aT5bFBpe4VNgo=" } ["2"]=> string(52) "Интернет и телекоммуникации" ["3"]=> string(217) "Товары и услуги, связанные с телекоммуникациями, в том числе кабельное и спутниковое обслуживание и доступ в Интернет." } [1]=> object(stdClass)#9 (3) { ["1"]=> object(stdClass)#8 (1) { ["1"]=> string(56) "AClZvXKrUJJ3kKBen2scP56BynOtGhf160i1F1LLmtBj3b/oh2dUFg8=" } ["2"]=> string(35) "Мобильные телефоны" ["3"]=> string(359) "Мобильные и сотовые телефоны, а также сопутствующая информация, например технические характеристики и сравнительный анализ товаров. В эту категорию не входят аксессуары для мобильных телефонов." } [2]=> object(stdClass)#11 (3) { ["1"]=> object(stdClass)#10 (1) { ["1"]=> string(56) "AClZvXL4W+khZ4O9SJiu97cTbTs2+0Wecf1IVNju8ffd4ysIT9PJ7XY=" } ["2"]=> string(73) "Мобильные телефоны и аксессуары для них" ["3"]=> string(283) "Мобильные телефоны, а также сопутствующие аксессуары и аппаратное обеспечение, например чехлы, моноподы для селфи, защитные экраны и зарядные устройства." } [3]=> object(stdClass)#13 (3) { ["1"]=> object(stdClass)#12 (1) { ["1"]=> string(56) "AClZvXLQ3gPoVwjQbokDpB3+nni4xURwH5+YlnwkqjYtUowjhiKvk8Q=" } ["2"]=> string(45) "ПК и бытовая электроника" ["3"]=> string(142) "Товары, услуги и информация, связанные с компьютерами и бытовой электроникой." } [4]=> object(stdClass)#15 (3) { ["1"]=> object(stdClass)#14 (1) { ["1"]=> string(56) "AClZvXLKYOGgOROaa32IUxU15jP89AtTM4dV24WKS+daMhqJMTNmeSY=" } ["2"]=> string(18) "Телефония" ["3"]=> string(181) "Товары, услуги, а также информационные и другие ресурсы, связанные с телефонией и голосовой связью." } } } ["10"]=> object(stdClass)#17 (1) { ["1"]=> string(76) "AClZvXLdGOShgJo+BM3apOUAFzQkE41z1/hiZhIY8eUlC7p7xXPm82P3dq7yXhbEI+tN/YHgdH4P" } } } ["2"]=> float(0) ["3"]=> string(5) "60609" ["4"]=> int(1) ["5"]=> string(0) "" ["6"]=> string(168) "ClD3Z2nP2P/////1/ff99fXV98nMyMrJz8rH9fHV883Hx8bMz83Oz8vOzv8A/v/+9f33/fX11ffJzMjKyc/Kx/Xx1fPNx8fGzM/Nzs/Lzs7//hABIWxUk293Pm+qOQAAAAAnMJaYSAFQAFoLCS8wxxaTatL1EAJgp7737gY=" ["7"]=> string(4) "3639" ["9"]=> int(0) } ["xsrf"]=> string(48) "ABOvogKaRsVZECZZJU-gDWrOqoP0CSqf7Q:1535467886413" }

Это был пример ответа, содержащий лишь одно объявление. Понять что к чему можно.
Да и у других запросов методы вполне по-человечески называются. Несколько примеров:

  • getWebPropertyMetricsToken
  • getAdDisplayLanguages
  • getArcSettings
  • getAdNetworkApprovals
  • getPubControlsCapabilities


Теоретически возможно. В бой?


Ладно, разгадать их общение возможно (теоретически), но это всё будет бесполезно, да теорией так и останется, если не сделать авторизацию в Google.

Авторизация. Или как войти в Google на php+cURL


Опять инструменты разработчика, выходим из учётной записи и смотрим на обмен данными. Детально не помню, ибо ничего там понять не смог. Огромное количество JS, похоже, что какие-то вычисления прямо на клиенте делаются, результаты на сервер отправляются. В общем, нечеловеку войти практически невозможно.

Думаем дальше. Куча JS. А если JS отключить? Не обделит же Google пользователей без JS возможностью авторизоваться? Что ж, попробуем без JS. Внешне окошко авторизации уже выглядит куда проще. Как и ранее, сначала вводим логин, а пароль на следующей странице. Самое главное, что и в плане HTML тоже намного проще! Обычный тэг «form» с обычными полями «input», правда не без кучи защитных или системных скрытых полей. Но скрытые поля — не проблема, ведь что на входе получили, то следующему скрипту и передали. И таким образом получилось войти в Google. А двухэтапная авторизация? Об этом позже. Сначала надо убедиться, что получится вытаскивать объявления для их досмотра, иначе это всё смысла не имеет.

Возможно ли теоретическое на практике?


В Google вошли — самое время проверить теорию разгадывания протоколов общения на практике. Пришлось повозиться с экспериментами и наблюдениями, внимательно наблюдать и записывать какие действия пользователя приводят к каким запросам, выявлять общие и меняющиеся элементы запроса, сопоставлять длинные непонятные значения, полученные с сервера и такими же длинными, отправляемыми обратно в следующем запросе. Это был дремучий лес, который со временем становился понятнее и прозрачнее.

Что нужно было сделать, чтобы понять, что продолжение имеет смысл?

  1. Войти в ЦПО.
  2. Получить список объявлений.
  3. Получить конкретное объявление (для начала текстовое).


Вход в ЦПО — самое простое, грубо говоря, просто переходим по ссылке. Получилось.

Подробности
Мы просто как бы переходим по ссылке, получаем ответ (который в данном случае не используем). Ещё нам нужно запросить и сохранить цифровой жетон для дальнейших запросов.

В AdSense на момент написания статьи есть два ЦПО. Назову их условно старый и новый.

Для старого ЦПО.

Post-запрос «без нагрузки»:

https://www.google.com/adsense/gwt-properties?pid=pub-0000000000000000&authuser=0&tpid=pub-0000000000000000&ov=3&hl=en

Ответ:































Нам нужна четвёртая строчка, а именно «syn.token.pb». Сохраняем это значение для дальнейшего формирования запросов.

Для нового ЦПО.

Post-запрос «без нагрузки»:

https://www.google.com/ads-publisher-controls/acx/5/darc/loader?onearcClient=adsense&pc=ca-pub-6928690776790362&tpid=pub-6928690776790362&hl=en

Ответ:

(function() {function loadAsyncOrDefer() {var scriptElement = document.createElement('script'); scriptElement.src = 'https:\/\/ssl.gstatic.com\/ads-publisher-controls\/onearc_20180822-12_RC00\/darc\/arc_app.dart.js';scriptElement.type = 'application\/javascript';scriptElement.defer = true;scriptElement.nonce = window['acxCspNonce'];scriptElement = document.head.appendChild(scriptElement); if ('_resourceTimingBuffer' in window) {_resourceTimingBuffer.add(scriptElement.src);}};loadAsyncOrDefer();})();window['__darc_app_params'] = {'onearcClient': 'ADSENSE','hl': 'ru','pc': 'ca-pub-6928690776790362','tpid': 'pub-6928690776790362',};window['__app_metadata'] = {'token': 'ABiMD8TT9vzK99SFB7iaI0ssBySxT9jjrQ:1535116725529','pre': '\/ads-publisher-controls\/acx','scs': 'https:\/\/ssl.gstatic.com\/ads-publisher-controls\/onearc_20180822-12_RC00','oacf': '\x7b\x221\x22:\x5b5,25,22,8,27,32,43,44,45,48,49,5,25,22,8,27,32,43,44,45,48,49,29,46\x5d\x7d','hats': 'ibhswcm2x2iztju5i6jbbzlkma',};

Нужная нам последовательность здесь:

'token': 'ABiMD8TT9vzK99SFB7iaI0ssBySxT9jjrQ:1535116725529'

Получение списка — задача интересная, ведь нужно передать кучу настроек — сообщить что мы хотим получить (тип объявлений, проверенные/новые/заблокированные, количество объявлений и прочее). Плюс цифровой жетон XSRF на каждый запрос. Получилось. В ответ пришёл большой объём данных, который содержал даже миниатюры изображений тех сайтов куда вели объявления. Ну и, конечно, ссылки на объявления.

Подробности
Сохранились черновики моих попыток разгадать какой параметр за что отвечает. Я их чуть облагородил вырезал весь мат и смайлики и выложил сюда. Сначала будет последовательность для старого ЦПО, а затем для нового.

Далее запросами буду называть названия методов (только для нового ЦПО, для старого метод указан в теле запроса) и json-строку, так как они являются «носителями» информации, всё остальное (адрес, заголовки, цифровые жетоны, прочие параметры) — «обёртка», они не принципиальны, про это всё расскажу позже.

Для старого ЦПО (переменная «params» json-запроса):

{
  "1":"ca-pub-6928690776790362",
  "2":{
    "1":0,   // Стартовая позиция, то есть начиная с какого по счёту показывать объявления
    "2":32,  // Кол-во запрашиваемых объявлений
    "3":0,   // 0 - незаблокированные, 1- заблокированные
    "4":{
        "1":{"1":"какая-то муть из предыдущего ответа"}    //Жетон из предыдущего ответа
        },
    "5":{
       "1":"video"  // поисковое слово
       "2":1,       // Если есть, то смотрим только непроверенные
       "3":1,       // Появляется если поставить только прогнозируемую блокировку
       "6":7,       // Объявления за указанное количество дней
       "16":[0],    // 0 - текстовые; 1 - графические; 2 - медийные. При включении всех этот параметр отсутствует.
       "17":0       // При включении медийных этот параметр пропадает
       }
    },
  "3":"-3945261286198141534" //Похоже, параметр не обязательный
}

Расшифровка есть, формируем запрос и получаем ответ.

Для старого ЦПО предварительно нужно получить жетон — сделать ещё один запрос перед самим запросом объявлений:

{"method":"getWebPropertyMetricsToken","params":"{\"1\":\"ca-pub-6928690776790362\"}","xsrf":"ABOvogKJ6-xmsNWK4Mbe_H5bT1xXhyj8SQ:1535115071772"}

Ответ:
{"result":{"1":{"1":{"1":"AClZvXKte+4mEwsFB7kw20LrbWQ6jOMxmK8j4At4Vxqc7w+5dDDYWIx2k1ldCvvGbAT59UClLSkQty6zyZZQSmgxKvpKhq22bKRfGy8ywt0B5L8WE53vo+YtI8ixM8Xe0RPixTjPtOLQA8sCZod+hvHxqU5Depi3I9XUV6JMn8uCOg67m+5oe5TT1L0OytnUBDIsjAaQ+kcldN23yGoppKKCs2Zf5XI6i7nk5QHehS8wvsDlugvkKSU3fUo3J+ZHJvoUXyCGLP3lP9Gh+6fOMir/SLrOJx8udRbtjTJhLsvXTXUN2QbjcEfFFAIaWfgMr5euHtYwYYWuMoI5ofZTc9L8sCY5pA0Q/CWyZ6QLH85XI70vxH6cBZtsnfrPLRh18cxSxFgzXuAwPHW8+CueCznqiHcY7gOhxQc2YWmSgwMIP9Cpgr089dWoB58wulcK0g+EqnTJiQdI9MMUj4zzLpu5DYja5ftP7lF3jeCSuKT9q70B9OqMDvlGlruZd2hhHe3k5S+LoyWo/4WZDUTvWpCMmnPzCP3R4OIQnrhS0s5ffOVxjyNHrXJXtrNhppap3BY4iByIn1cowMfVFfx3hNep0JW59db9fVuXKaSy/mqHZKC1ToRM/UyCoSZ9ZjY/Ot091ptURLRYoTFal5TBbMKISgxn5UCz4vSoxVe1fC64dwXHatSzCCg9AjJOpKR4p/9smxOaKg73pmMHsEY98I6TJhvaeJ9o6lcHsG8PZnB6xNS4ZJHBtN1baHkrCHOfqaepMVyRCF2kPNhr9SgujjTTbiKGMUO3UVamOQQ5/EckTgFMr0PIda7PPw7op8qFEhxZmkoo9KgERcYLGHxzGePjfo0IiNbf7k50lgDipwk5ag3CI0tw3CtDicQn6isHwKOmlfSctrEGv/Fjlmcgjhl1sTAL/rTWxDCABKN7/OhdysBAOq0j6viFgzjM8WI0ZuYPIVIm19CQ+YGcOx77oiyxev+3sAj7uSJoYFslmgiZV4jrF5P+b+U/5fknRf2Ho8plAUh4AHweXMeaPFYZAYooe6jC79EzgizqXvx1H/HrKKQcaXdDZ1ivoOM/7DtzJbawzO7ALUnHkqR1ZYmw3+3E/pmsDXedYgzERWYWvJltS+P46iWYOS43SUVw+whDWZnjJOwVOFFLDWcg4ykfzNmbq4B/vUibrV1dCiRpTIXSP92xk1I8MCfQGiptqo5MiKttqJ9Orj7nrGXEDz5pJBTTem919nz5rNIjI/sus3GZ+G4rBE+9i1sJN0jxszvpRD2AKsl1KSOrPCuOBhpNbD2HnFgQd+EUw8CpH2MLZlrZ8l3cqzDVc5aeCQ1eiUKlONlZpIxZi5wE5HyKZRxC8ljtX5xe+Fpg8R8/yDarvAkjeb0yKzN/e893nEVz3CmF68pphNp71kjJtvwBS2JtSWhFc81Ys51GEw\u003d\u003d"}}},"xsrf":"ABOvogJLbcTkcBxU_TCJddIrW4L-mVwPcw:1535115072920"}

Этот огромный жетон (»1»: «AClZ…») нам понадобится для запроса объявлений.

Запрос объявлений:

{"method":"searchArcApprovals","params":"{"1":"ca-pub-6928690776790362","2":{"1":0,"2":24,"3":0,"4":{"1":{"1":"AClZvXKte+4mEwsFB7kw20LrbWQ6jOMxmK8j4At4Vxqc7w+5dDDYWIx2k1ldCvvGbAT59UClLSkQty6zyZZQSmgxKvpKhq22bKRfGy8ywt0B5L8WE53vo+YtI8ixM8Xe0RPixTjPtOLQA8sCZod+hvHxqU5Depi3I9XUV6JMn8uCOg67m+5oe5TT1L0OytnUBDIsjAaQ+kcldN23yGoppKKCs2Zf5XI6i7nk5QHehS8wvsDlugvkKSU3fUo3J+ZHJvoUXyCGLP3lP9Gh+6fOMir/SLrOJx8udRbtjTJhLsvXTXUN2QbjcEfFFAIaWfgMr5euHtYwYYWuMoI5ofZTc9L8sCY5pA0Q/CWyZ6QLH85XI70vxH6cBZtsnfrPLRh18cxSxFgzXuAwPHW8+CueCznqiHcY7gOhxQc2YWmSgwMIP9Cpgr089dWoB58wulcK0g+EqnTJiQdI9MMUj4zzLpu5DYja5ftP7lF3jeCSuKT9q70B9OqMDvlGlruZd2hhHe3k5S+LoyWo/4WZDUTvWpCMmnPzCP3R4OIQnrhS0s5ffOVxjyNHrXJXtrNhppap3BY4iByIn1cowMfVFfx3hNep0JW59db9fVuXKaSy/mqHZKC1ToRM/UyCoSZ9ZjY/Ot091ptURLRYoTFal5TBbMKISgxn5UCz4vSoxVe1fC64dwXHatSzCCg9AjJOpKR4p/9smxOaKg73pmMHsEY98I6TJhvaeJ9o6lcHsG8PZnB6xNS4ZJHBtN1baHkrCHOfqaepMVyRCF2kPNhr9SgujjTTbiKGMUO3UVamOQQ5/EckTgFMr0PIda7PPw7op8qFEhxZmkoo9KgERcYLGHxzGePjfo0IiNbf7k50lgDipwk5ag3CI0tw3CtDicQn6isHwKOmlfSctrEGv/Fjlmcgjhl1sTAL/rTWxDCABKN7/OhdysBAOq0j6viFgzjM8WI0ZuYPIVIm19CQ+YGcOx77oiyxev+3sAj7uSJoYFslmgiZV4jrF5P+b+U/5fknRf2Ho8plAUh4AHweXMeaPFYZAYooe6jC79EzgizqXvx1H/HrKKQcaXdDZ1ivoOM/7DtzJbawzO7ALUnHkqR1ZYmw3+3E/pmsDXedYgzERWYWvJltS+P46iWYOS43SUVw+whDWZnjJOwVOFFLDWcg4ykfzNmbq4B/vUibrV1dCiRpTIXSP92xk1I8MCfQGiptqo5MiKttqJ9Orj7nrGXEDz5pJBTTem919nz5rNIjI/sus3GZ+G4rBE+9i1sJN0jxszvpRD2AKsl1KSOrPCuOBhpNbD2HnFgQd+EUw8CpH2MLZlrZ8l3cqzDVc5aeCQ1eiUKlONlZpIxZi5wE5HyKZRxC8ljtX5xe+Fpg8R8/yDarvAkjeb0yKzN/e893nEVz3CmF68pphNp71kjJtvwBS2JtSWhFc81Ys51GEw\u003d\u003d"}}},"3":""}","xsrf":"ABOvogI3FCm29t4pdIded8L-Q98R0Voy-Q:1535121289188"}

Перевожу раздел 2 переменной «params»:
Дорогой Google, покажи нам, пожалуйста:
начиная с нулевого объявления ("1":0),
24 объявления ("2":24),
незаблокированных ("3":0),
свежий одноразовый пароль: AClZvX....

Ряд параметров можно не указывать, они принимают значения по-умолчанию:

  • тип объявлений: все;
  • период: все доступные;
  • прогнозируемая блокировка: нет;
  • показывать только непроверенные: нет.

В ответ приходят десятки или сотни килобайт в зависимости от количества запрошенных объявлений. Самое тяжёлое — это графика, «вытянутая» в тексте (data: image/gif; base64…). А если непроверенных нет, то ответ прост:
{"result":{"4":1,"5":"","8":"0","9":0},"xsrf":"ABOvogLWqmyC7KH1zfvmPxk-Y69-Jzj5XQ:1535115074392"}

Если б объявления были они бы содержались здесь: result→{5}.

Для нового ЦПО:

{
  "1":"ca-pub-6928690776790362",
  "2":{
    "1":10,            // Стартовая позиция, то есть начиная с какого по счёту показывать объявления
    "2":7,             // Кол-во запрашиваемых объявлений
    "3":11,            // проверенные - 10; заблокированные - 1; Непроверенные - 11;
    "5":{
       "6":3           // Объявления за указанное количество дней
       "7":3534        // номер рекламной сети
       "14":"en"       // язык
       "16":[0]        // 0 - текстовые; 1 - графические; 2 - медийные.
       "18":"dfd.com"  // домен издателя
       "24":"video"    // поисковое слово
       },
    "7":""},                  // Жетон из предыдущего ответа для перехода к следующей странице
  "3":"-2876348936240321457", // Жетон из предыдущего ответа для перехода к следующей странице
  "5":true                    // Просто истина и всё. Всегда.
}

Предварительных запросов делать уже не нужно, можно сразу запрашивать объявления.
SearchApprovals (это метод)
{"1":"ca-pub-6928690776790362","2":{"2":100,"3":11,"5":{"16":[0]},"7":""},"5":true}

Google, выдай, пожалуйста:
100 объявлений ("2":100),
непроверенных ("3":11),
текстовых ("5":{"16":[0]},
идентификатора сессии поиска у меня пока нет ("7":"")

Необязательные параметры и умолчания:

  • порядковый номер первого запрашиваемого объявления: 0;
  • период: все доступные;

В ответ мы получаем практически то же самое, что в случае старого ЦПО. Отличается только одним словом — названием контейнера с данными. В старом это «result», в новом — «default».

Получение конкретного объявления — всё просто, берём из предыдущего ответа ссылку и скачиваем объявление. Здесь уже никакой защиты, доступ свободный для всех.

Подробности
Ссылка на объявление. Будем её искать в предыдущем ответе, где мы получили много-много килобайт текста в ответ на запрос объявлений.

Чтоб не было слишком много непонятного кода привожу ответ на запрос одного объявления (да и то нещадно порезанный, он был раз в 10 больше, оставлено только самое главное на данный момент):

{"result":{"1":[{"1":0,"3":0,"4":{"1":"AClZvXJ2t4wiEZ/VZ0i54m0Qtqpi2DTqkI1kaPMTRi4LnsQn0iR5K1xBlFpS1xmJV7ko4a6qx5RcTkp7CzVjwoy5UDSWZ5jOCPLGRcoQdDt+wOk46bdr0yA\u003d"},"5":{"1":82,"2":0,"3":0,"4":"\u00GQ","13":"https://adwords-displayads.googleusercontent.com/da/b/preview.js?client\u003dasfe-arc-external-preview\u0026obfuscatedCustomerId\u003d5240877441\u0026creativeId\u003d288930210411\u0026htmlParentId\u003dad-parent-id-6A2DE3D206234468F53C743C0EEACD67A59E6C5B62C0371F770419826258CB1AD9591F60\u0026showVariations\u003dtrue\u0026sig\u003dACiVB_yMUjLwDjRO2T-0VAaVuRPt8uLHGQ","14":"https://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/","15":"","17":"","18":"","20":"adv-5594449542310820","21":["domain1.com","domain2.com"]},"6":{"5":"-6668648012302470727","9":0},"7":1,"9":{"3":[{"1":{"1":"AClZvXLE9HJbFYq9TrAsXFgV4YkXsQt9lXp1xWjSB5aT5bFBpe4VNgo\u003d"},"2":"\u041/YHgdH4P"}}],"2":0.0,"3":"59917","4":1,"5":"","6":"ClD3Z2nP2P/////1/ff9oPjm7gU\u003d","7":"5751","9":0},"xsrf":"ABOvogJJJuNM1d0i22yN48ibBAY8vpvC_A:1535125743731"}

Из параметра {13} можно вытащить ссылку на объявление:

https://adwords-displayads.googleusercontent.com/da/b/preview.js? client=asfe-arc-external-preview&obfuscatedCustomerId=5240877441&creativeId=288930210411&htmlParentId=ad-parent-id-6A2DE3D206234468F53C743C0EEACD67A59E6C5B62C0371F770419826258CB1AD9591F60&showVariations=true&sig=ACiVB_yMUjLwDjRO2T-0VAaVuRPt8uLHGQ.

Какое-то время (дни, может недели) ссылка будет жить и по ней любой желающий сможет получить объявление. Там примерно 100 — 150 килобайт и в самом низу (и не только) можно найти отрывки из текста объявления.

Кроме это важный параметр — это внутренний идентификатор объявления, который мы далее будем использовать для управления (блокировка/разблокировка объявления, блокировка/разблокировка аккаунта AdWords, который это объявление откручивает, запрос статистики показов, установка пометки «проверено», отправка сообщения о нарушении правил). Хранится он тут:
result→{1}→{4}→{1}.

Выглядит так:

AClZvXJ2t4wiEZ/VZ0i54m0Qtqpi2DTqkI1kaPMTRi4LnsQn0iR5K1xBlFpS1xmJV7ko4a6qx5RcTkp7CzVjwoy5UDSWZ5jOCPLGRcoQdDt+wOk46bdr0yA=

Его длина 120 символов (с редкими исключениями).

Всего в этим потоке данных довольно много информации:

  • Тип объявления.
  • Целевой URL.
  • Домены, на которых откручивалось.
  • Сведения о рекламодателе (название, если есть и идентификатор).
  • Качественная характеристика кол-ва показов, например, «высокое».
  • Три миниатюры посадочной страницы в виде data: image.
  • Категория, куда относится объявление, например, «Телефония».


Результат получен — автоматизации поддаётся. Далее был наведён порядок в функциях, ибо рабочий прототип был ужасен, хотелось лишь скорее понять удастся ли процесс автоматизировать. Первая версия был предложена людям и началось доделывание и исправление ошибок. Первая проблема — «двухэтапники» не могли авторизоваться.

Двухэтапная авторизация


Если пойти проверять как это выглядит при выключенном JS, то можно увидеть множество вариантов авторизации: пароль по SMS, одноразовые пароли с бумажки, через приложение…
Каждый вариант автоматизировать, чтобы всем было удобно — это с ума сойти можно.

Спасение разработчика


Когда без JS в Chrome смотрел на механизм двухэтапной авторизации увидел ссылочку на выбор другого метода, за это и зацепился. Какой бы метод не был выбран по-умолчанию, всегда есть вариант перейти к выбору и выбрать SMS. Это и стало настоящим спасением. Конечно, пришлось делать проверку выбранного по-умолчанию метода, и в случае «неправильного» метода «нажимать» кнопку смены и выбирать «одноразовый пароль по SMS».

Для самой авторизации лишь сделал сохранение в файл промежуточных данных из формы (та самая куча всяких скрытых полей) и форму ввода одноразового пароля. Всё, «двухэтапники» тоже смогли войти.

Завершение процесс создания


Основная задача была выполнена — любой мог установить и использовать автоматизированное решение для периодической фильтрации рекламы на своих сайтах.

Далее шли доработки, доделки, исправление недостатков…
… выявленных как пользователями так и самостоятельно, доработка внешнего оформления (интуитивно понятное для автора было непонятным почти всем остальным).
Так же были доделаны и добавлены различные функции и фильтры для поиска неугодных объявлений. Например, для автоматического определения мешанины кириллицы и латиницы. Нормальные рекламодатели так объявления не составляют, но иногда бывают ошибки в виде подмешивания в русские слова одного символа латиницы (популярные ошибки в фильтре тоже учтены).

Добавленные для удобства дополнения:

  • Список заблокированных рекламодателей.
  • Список заблокированных доменов.
  • Табличка доходов.
  • Ссылки AdSense.

Список заблокированных рекламодателей — это возможность смотреть и править, причём удобнее (но не красивее внешне) чем в штатном интерфейсе! Плюс есть возможность разблокирования «оптом», которой нету в штатном AdSense.

Список заблокированных доменов — аналогично предыдущему списку.

Возможность работы с AdX (через AdManager, куда AdX недавно переехал).

Доработок много, выше перечислены самые интересные на мой взгляд.

Функции отправки запроса и приёма результата


Ранее писал про запросы в виде json-строк, а подробнее обещал раскрыть позже.

Когда всё это делал нового ЦПО ещё не было, следовательно всё делалось для старого, с него и начнём.

Общение со старым ЦПО


С помощью наблюдений удалось выяснить, что основной обмен запросами идёт по одному адресу:

https://www.google.com/adsense/gp/creativeReview?ov=3&pid=pub-6928690776790362&authuser=0&tpid=pub-6928690776790362(&hl=ru)

То, что в скобках бывает не всегда, это просто параметр, который указывает на язык ответа, его можно применить почти ко всем продуктам Google. Это важно, так как у меня везде используется английский и ПО распознаёт некоторые параметры, ожидая ответа на английском.

Кроме адреса есть стандартная форма передаваемых post-данных (в инструментах разработчика они видны в разделе «Request Payload») — это json-строка с переменными method, params и xsrf:

{"method":"getArcSettings","params":"{\"1\":[\"ca-pub-6928690776790362\"]}","xsrf":"ABOvogJlvXKkBQUbPYEsM04recgCsukFMg:1535467881599"}


method — тут, вроде, всё ясно.
params — в зависимости от метода свой стандартный формат передаваемой json-строки.
xsrf — выше описано изначальное получение цифрового жетона, который мы используем для запроса, а в ответе нам приходит новый XSRF-token для следующего запроса.

Ответ тоже приходит в виде json-строки из частей result (запрошенная информация) и xsrf:

{"result":{"1":[{"1":"ca-pub-6928690776790362","2":{"1":"ca-pub-6928690776790362","2":0},"3":{"1":"ca-pub-6928690776790362","2":0}}]},"xsrf":"ABOvogIH7wJjD8t1xmuu8WbGplQowqjjJA:1535467883406"}


php-код функции
function creative_review($method, $params)
{
    $xsrftoken = file_get_contents($GLOBALS['xsrftoken_file']);

    $creativeReview = new stdClass(); //to make json request string
    $creativeReview->method = $method;
    $creativeReview->params = $params;
    $creativeReview->xsrf = $xsrftoken;
    $creativeReview_post_request = json_encode($creativeReview);
    unset($creativeReview);

    $result = curl_post($GLOBALS['creative_review_req_string'], $creativeReview_post_request, $GLOBALS['arc_tab_req_string'], $GLOBALS['myheaders']);

    $result = json_decode($result); // decode result string

    if ($result->xsrf)
        file_put_contents($GLOBALS['xsrftoken_file'], $result->xsrf); // Renew standard XSRF token

    return $result;
}

Где своя функция post-запроса — curl_post ($url, $postfields, $referer, $myheaders).

Переменные названы чтоб понятно было что есть что.
$myheaders почти во всех запросах содержит эти два заголовка:

accept-language:en-US;q=1,en;q=0.4
content-type:application/javascript; charset=UTF-8

$GLOBALS['creative_review_req_string']:

https://www.google.com/adsense/gp/creativeReview?ov=3&pid=pub-6928690776790362&authuser=0&tpid=pub-6928690776790362&hl=en

Тот самый адрес, через который идёт обмен данными.

$GLOBALS['arc_tab_req_string']:

https://www.google.com/adsense/new/u/0/pub-6928690776790362/main/allowAndBlockAds?webPropertyCode=ca-pub-6928690776790362&tab=arcTab

Это значение referer было и в оригинальных запросах, оно тоже не меняется.


Общение с новым ЦПО


Здесь с адресом для запроса сложнее — он меняется. Есть только начальная общая часть. Схема такая:

Общая часть + метод + '?' + GET-параметры + rpcTrackingId = <повторение предыдущих GET-параметров в URL-кодировке>:1.

https://www.google.com/ads-publisher-controls/acx/5/proto/creativereview/GetArcSettings?hl=ru&pc=ca-pub-6928690776790362&onearcClient=adsense&rpcTrackingId=%2Fads-publisher-controls%2Facx%2F5%2Fproto%2Fcreativereview%2FGetArcSettings%3Fhl%3Dru%26pc%3Dca-pub-6928690776790362%26onearcClient%3Dadsense%3A1

XSRF-token здесь передаётся в заголовке 'x-framework-xsrf-token' и он многоразовый, следовательно, в ответа

© Habrahabr.ru