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

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

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


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

О котором из «воровств» я? О том, где мы, гуляя по интернету, нажимаем на кнопочку «смотреть видео»,  грузится какая-то страница, видео почему-то не проигрывается, мы уходим и гуляем дальше, а на самом деле мы «добровольно» подключили себе услугу получать что-то, что никто никогда не видел за символическую плату 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 построен на AngularDart, там с виду всё красиво и довольно сложно с точки зрения устройства.

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

Передаваемая строка.
{"method":"searchArcApprovals","params":"{\"1\":\"ca-pub-8958890276790964\",\"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) "<div id="ad-parent-id-6A2DE3D206234468F53C743C0EEACD67A59E6C5B62C0371F770419826258CB1AD9591F60"></div>"
          ["5"]=>
          string(102) "<div id="ad-parent-id-6A2DE3D206234468F53C743C0EEACD67A59E6C5B62C0371F770419826258CB1AD9591F60"></div>"
          ["6"]=>
          string(355) "<div>Многоформатные<span id='multi-format-tooltip'></span></div><a class='arc-url-link-ellipsis' target='_blank' href='https://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/' title='https://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/'>https://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/</a>"
          ["7"]=>
          string(1066) "<div class='arc-one-by-one-legend'>Тип объявления</div><div class='arc-one-by-one-data'>Многоформатные<span id='multi-format-tooltip'></span></div><div class='arc-one-by-one-legend'>Целевой URL</div><div class='arc-one-by-one-data'><a class='arc-url-link-ellipsis' target='_blank' href='https://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/' title='https://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/'>https://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/</a></div><div class='arc-one-by-one-legend'>Домены издателей</div><div class='arc-one-by-one-data'>4aynikam.ru</div><div class='arc-one-by-one-data'>androidphone.su</div><div class='arc-one-by-one-data'>androidphones.ru</div><div class='arc-one-by-one-data'>full-repair.com</div><div class='arc-one-by-one-data'>howgadget.com</div><div class='arc-one-by-one-legend'>Обнаруженный рекламодатель<span id='adx-advertiser-tooltip'></span></div><div class='arc-one-by-one-data'>DNS Shop</div>"
          ["8"]=>
          string(98) "<div><span class='arc-impression-score high'>ВЫСОКОЕ</span> число показов</div>"
          ["9"]=>
          object(stdClass)#4 (3) {
            ["1"]=>
            string(4191) "<a href="https://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/" target="_blank"><img onerror="this.src='data:image/gif;base64,RCw" border=0 alt=""></a>"
            ["2"]=>
            string(4191) "<a href="https://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/" target="_blank"><img onerror="this.src='data:image/gif;base64,R1w" border=0 alt=""></a>"
            ["3"]=>
            string(4191) "<a href="https://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/" target="_blank"><img onerror="this.src='data:image/gif;base64,Rg" border=0 alt=""></a>"
          }
          ["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-8958890276790964&authuser=0&tpid=pub-8958890276790964&ov=3&hl=en

Ответ:

<meta name="gwt:property" content="usePropertyService=true">
<meta name="gwt:property" content="applicationType=ASFE3">
<meta name="gwt:property" content="syn.token=ABOvogJ1yQyL9pgHcGYM-J3OLj_9VSh31w:1535115071772">
<meta name="gwt:property" content="syn.token.pb=ABOvogKJ6-xmsNWK4Mbe_H5bT1xXhyj8SQ:1535115071772">
<meta name="gwt:property" content="syn.login=XXXXXX@gmail.com">
<meta name="gwt:property" content="syn.csi.backendUrl=">
<meta name="gwt:property" content="syn.helpCenterUrl=//support.google.com/adsense/">
<meta name="gwt:property" content="syn.helpHost=//support.google.com">
<meta name="gwt:property" content="syn.helpCenterUri=/adsense">
<meta name="gwt:property" content="syn.newHelpHost=https://clients6.google.com">
<meta name="gwt:property" content="syn.newHelpCenterUri=/adsense">
<meta name="gwt:property" content="syn.helpCenterGaiaAuthDisabled=false">
<meta name="gwt:property" content="syn.billing3BaseUri=https://bpui0.google.com">
<meta name="gwt:property" content="syn.contextPath=/adsense">
<meta name="gwt:property" content="syn.userLanguage=en-US">
<meta name="gwt:property" content="syn.bruschettaContextPath=/adsense/new">
<meta name="gwt:property" content="userProfileImageUrl=https://lh5.googleusercontent.com/-v7nuoAI4eEQ/AAAAAAAAAAI/AAAAAAAAAAA/AT3-yjmKyg8/s96/photo.jpg">
<meta name="gwt:property" content="userDisplayName="Имя Фамилия">
<meta name="gwt:property" content="userSettingsUrl=https://www.google.com/settings">
<meta name="gwt:property" content="googlePlusProfileUrl=https://plus.google.com/me">
<meta name="gwt:property" content="googlePrivacyUrl=http://www.google.com/intl/en_US/policies/privacy/">
<meta name="gwt:property" content="syn.features=562,465,612,604,616,618">
<meta name="gwt:property" content="analyticsHomePageUrl=https://www.google.com/analytics/web/">
<meta name="gwt:property" content="disableDebugIds=true">
<meta name="gwt:property" content="syn.pubControlsCapabilitiesLoadTimeout=5000">
<meta name="gwt:property" content="pid=pub-8958890276790964">
<meta name="gwt:property" content="tpid=pub-8958890276790964">
<meta name="gwt:property" content="syn.asfeGtmCampaignId=GTM-K7ТМWZ">

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

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

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

https://www.google.com/ads-publisher-controls/acx/5/darc/loader?onearcClient=adsense&pc=ca-pub-8958890276790964&tpid=pub-8958890276790964&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-8958890276790964','tpid': 'pub-8958890276790964',};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-8958890276790964",
  "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-8958890276790964\"}","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-8958890276790964","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-8958890276790964",
  "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-8958890276790964","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-8958890276790964&authuser=0&tpid=pub-8958890276790964(&hl=ru)


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

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

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

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

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

{"result":{"1":[{"1":"ca-pub-8958890276790964","2":{"1":"ca-pub-8958890276790964","2":0},"3":{"1":"ca-pub-8958890276790964","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-8958890276790964&authuser=0&tpid=pub-8958890276790964&hl=en


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

$GLOBALS['arc_tab_req_string']:

https://www.google.com/adsense/new/u/0/pub-8958890276790964/main/allowAndBlockAds?webPropertyCode=ca-pub-8958890276790964&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-8958890276790964&onearcClient=adsense&rpcTrackingId=%2Fads-publisher-controls%2Facx%2F5%2Fproto%2Fcreativereview%2FGetArcSettings%3Fhl%3Dru%26pc%3Dca-pub-8958890276790964%26onearcClient%3Dadsense%3A1

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

php-код функции
function creative_review_new($method, $params)
{
    if (!isset($GLOBALS['xsrftoken_new']))
        $GLOBALS['xsrftoken_new'] = file_get_contents($GLOBALS['temp_folder'] . 'xsrftoken_new.txt');

    $myheaders = $GLOBALS['myheaders_new'];
    $myheaders[] = 'x-framework-xsrf-token:' . $xsrftoken;

    $query['pc'] = 'ca-' . $GLOBALS['pub_id'];
    $query['onearcClient'] = 'adsense';
    $query['hl'] = 'en_US';

    foreach ($query as $index => $value)
        $rpc[] = $index . '=' . $value;

    $append = ':1';
    $query['rpcTrackingId'] = $GLOBALS['creative_review_new_string'] . $method . '?' . implode('&', $rpc) . $append;
    $query = http_build_query($query);
    $url = 'https://www.google.com' . $GLOBALS['creative_review_new_string'] . $method . '?' . $query;

    $result = curl_post($url, $params, $GLOBALS['new_arc_tab_req_string'], $myheaders);

    if (mb_strpos($result, 'Error 400 (Not Found)', 0, 'UTF-8') !== false)
    {
        return '-32000 XSRF token validation';
    }

    $list = explode("\n", $result, 2);
    $result = $list[1];

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

    if (@$result->default->{5})
        file_put_contents($GLOBALS['temp_folder'] . 'some_digi_token.txt', $result->
            default->{5}); // Renew digi token
    if (@$result->default->{6})
        file_put_contents($GLOBALS['temp_folder'] . 'some_long_token.txt', $result->
            default->{6}); // Renew this long token

    return $result;
}

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

Здесь $myheaders немного отличаются (javascript > json):

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


$GLOBALS['creative_review_new_string']:

/ads-publisher-controls/acx/5/proto/creativereview/
Небольшая постоянная часть.

$GLOBALS['new_arc_tab_req_string']:

https://www.google.com/adsense/new/u/0/pub-8958890276790964/arc/ca-pub-8958890276790964
Это значение referer было и в оригинальных запросах, оно не меняется.

Снизу есть обновление двух разных жетонов. Это необходимо для «листания» страниц с объявлениями. Здесь механизм запроса второй и последующей страницы не стандартный («покажи мне 10 объявления, начиная с 30-го»). Здесь мы передаём сколько объявлений нам надо показать и один из жетонов из предыдущего ответа, порядковый номер первого запрашиваемого объявления не передаём.


Функция запроса списка доменов и управления ими


Она практически такая же как функция общения со старым ЦПО, отличается только адресом обращения.

php-код функции
function blocking_controls($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['blocking_controls_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;
}

$GLOBALS['blocking_controls_req_string']:
https://www.google.com/adsense/gp/blockingControls?ov=3&pid=pub-8958890276790964&authuser=0&tpid=pub-8958890276790964
Адрес для обмена данными.

XSRF-жетоны сохраняются на диск в файл, это необходимо чтобы работали запросы на блокировку/разблокировку объявлений, аккаунтов AdWords и прочих действий прямо из панели управления без необходимости запрашивать новый.

Обработка полученных ответов


Данные приходят либо в виде json-строк (ответы получаемые тремя функциями выше) и в виде JS-кода (запрашиваемые объявления), где ряд символов «зашифрован» шестнадцатеричной кодировкой (\x<код символа из двух символов знаков>).

Отрывок из того объявления, ссылка на которое есть выше:
target\x3d_blank title\x3d\x22\x22\x3e\x3cspan\x3eКупи Xiaomi Redmi S2 и получи Redmi 5 \x3cbr\x3eв подарок. С 24 по 26 августа. \x3cbr\x3eПодробнее на сайте.

Для json есть в php функция, которая на выходе даст хоть объект, хоть массив.
Для «косоиксов» где-то в сети нашёл небольшую функцию, которая приводит данные к человеческому виду.

php-код функции
function hex_repl($html) 
{
    $i = 256;
    while ($i >= 0) {
        $hex = dechex($i);
        $html = str_ireplace("\x$hex", chr($i), $html);
        $i--;
    }
    return $html;
}

Результат расшифровки:
target=_blank title=""><span>Купи Xiaomi Redmi S2 и получи Redmi 5 <br>в подарок. С 24 по 26 августа. <br>Подробнее на сайте.

Распознавание объявлений


Текстовые. Начинал я с них. Они были важнее и, как оказалось, с ними было всё намного проще. Их только два вида: старые, с одним заголовком (которых уже практически не осталось) и новые, с двумя заголовками.

Объявление приходит уже в виде HTML-кода, но кроме объявления в получаемом ответе содержится очень много ненужных нам данных — код Javascript (даже не вникал в суть этого кода).

Распознавание в итоге свелось к следующим шагам:

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

Заголовки, текст и ссылка содержат определённые классы, за них и «цеплялся» распознавальщик.

Что где содержится и функция обработки текстовых объявлений
rhtitleline1 — заголовок 1;
rhtitleline2 — заголовок 2;
rhtitle — заголовок (только для объявлений с одним заголовком);
rhbody — текст объявления;
rhurl — отображаемый URL.

function text_ad($html) 
{
    $list = explode('</head>', $html);
    $ad_html = array_pop($list);
    unset($list, $html);

    $dom = new DOMDocument('1.0', 'UTF-8');
    @$dom->loadHTML($ad_html);
    unset($ad_html);

    foreach ($dom->getElementsByTagName('a') as $a_node) {

        if (stripos($a_node->getAttribute('class'), 'rhtitleline1') !== false) {
            $ad['header1'] = $a_node->textContent;
            continue;
        }
            
        if (stripos($a_node->getAttribute('class'), 'rhtitleline2') !== false) {
            $ad['header2'] = $a_node->textContent;
            continue;
        }

        if (stripos($a_node->getAttribute('class'), 'rhbody') !== false) {
            $ad['body'] = $a_node->textContent;
            continue;
        }
            
        //Old ads (with just 1 header) support
        if (stripos($a_node->getAttribute('class'), 'rhtitle ') !== false || stripos($a_node->getAttribute('class'), 'rhtitle"') !== false) {
            $ad['header1'] = $a_node->textContent;
            continue;
        }

        if (stripos($a_node->getAttribute('class'), 'rhurl ') !== false || stripos($a_node->getAttribute('class'), 'rhurl"') !== false) {
            $ad['displayUrl'] = $a_node->textContent;
            continue;
        }
    }

    $fulltext = implode(' ', $ad);
    $ad['fulltext'] = $fulltext;

    if (!isset($GLOBALS['set_gl']['utf8_off']))
        foreach ($ad as $index => $value)
            $ad[$index] = utf8_decode($value);

    return $ad;
}

Первые три строчки — обрезка лишнего текста. Всё нужное в самом конце.

$fulltext — все заголовки и тексты объявления для дальнейшей проверки.

utf8_decode применяется или нет в зависимости от выбранной пользователем настройки. На разных системах DOMDocument в разных кодировках выдаёт ответ. Чтобы у всех всё правильно отображалось внедрено такое преобразование.


Графические. В них проверяется только целевой URL. Распознавания картинок нет, сохранения картинок для досмотра тоже (ибо картинки при желании можно и в ЦПО посмотреть). Не вижу здесь смысла изобретать велосипед (скорее всего, кривой и никому не нужный).

Мультимедийные. Под этим общим названием скрывается целый ряд разных объявлений:

  • Многоформатные (Multi-Format).
  • Медийные (Rich Media).
  • Произвольный шаблон (HTML5).

Для многоформатных создано 3 функции распознавания в зависимости от типа объявления.
Для медийных создано 2 функции.

Для HTML5 создано 3 функции.

Фильтрация


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

  • Наличие в домене «blogspot.com».
  • Наличие в словах смеси кириллицы и латиницы.
  • Наличие «плохих» слов (список «плохих» слов настраивается пользователем).
  • Наличие перенаправления пользователя на домен, отличный от исходного.

Отчёт о работе


По итогам фильтрации составляется отчёт о проделанной работе.
Он строится в виде списка объявлений по каждому фильтру в своей графе, плюс графа для «хороших» объявлений, в отчёт включается следующая информация:

  • Идентификатор и текстовое название рекламодателя, если последнее есть.
  • Причина блокировки (только для заблокированных).
  • Заголовки и текст объявления.
  • Целевой и отображаемый URL.
  • Дата и время проверки.
  • Суммарное количество просмотров, что успело набрать объявление (только для заблокированных).
  • Ссылки для блокрования/разблокирования объявления и аккаунта рекламодателя.
  • Ссылки для блокировки целевого URL или домена.
  • Ссылка для подачи жалобы на объявление (есть в новом ЦПО).
  • Ссылки для добавления различных частей объявления в «белый список».
  • Ссылка на удаление объявления из отчёта.

Внешний вид основан на базе старого ЦПО (и единственном на момент создания оформления).


Кликабельно


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

Немного о безопасности


Можно сделать ограниченный доступ к панели управления (чтобы самому управлять из одного места) или «всемирный», чтобы из любого места можно было управлять.

Первый случай безопасный — никто не влезет, если за рабочий ПК не сядет. Во втором случае адрес, где стоит ПО надо держать в секрете, плюс предусмотрена установка пароля на вход в панель управления. Чтобы Ваш секретный адрес не «просочился» при переходах по ссылкам на сторонние сайты (из объявлений) сделано следующее:

  • В каждой внешней ссылке стоит такой атрибут
    rel="noreferrer"
  • Чтобы referer передавался только в пределах одного домена в head стоит тэг:
    <meta name="referrer" content="same-origin">
  • Все внешние ссылки идут через «очиститель referer»:
    http://nullrefer.com/?http://free.da...

Результат автоматизации


24 часа 7 дней в неделю все вновь появившиеся в ЦПО объявления досматриваются с интервалом в пару-тройку минут. В результате чего неугодные (по критериям, заданным пользователем) отправляются в раздел «заблокировано». Точно никогда не считал, но примерно из 100 заблокированных штук 90 — 95 заблокированы не зря. Из ста «чистых» по мнению ПО в среднем менее одного «плохого».

Что я называю «плохими объявлениями»? Всё что ведёт на мобильные подписки, всё что предлагает «скачать», просто скачать или «скачать файл» без какой-либо конкретики вообще, всё что предлагает «смотреть видео», опять же без каких-либо подробностей, всё что ведёт совсем не туда, о чём указано в заголовке и тексте объявления, любые упоминания казино в странах, где это запрещено законом.

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

Стало меньше и воровства в виде неосознанных подписок даже без карты «МегаФона»!

А причём здесь карта «МегаФона»?
По ссылке выше есть такое «весьма интересное» «ключевое преимущество карты» «дополнительная защита денежных средств»:
Дополнительно к этому «МегаФон» всем пользователям карты подключает бесплатную услугу, которая блокирует неосознанные подписки. Таким образом, владельцы карт гарантировано защищены от нежелательных списаний с мобильного счёта.

Но не у всех пользователей наших сайтов есть «Мегафонкарты» и аналоги других ОПСОСов.
Поэтому, господа, защищайте посетителей своих ресурсов от нежелательных списаний самостоятельно!

Проект с открытым исходным кодом есть на GitHub.

Комментарии (62)


  1. ton1
    05.09.2018 17:17
    -1

    del


  1. achekalin
    05.09.2018 17:19

    Спасибо вам!

    Дополнительно к этому «МегаФон» всем пользователям карты подключает бесплатную услугу, которая блокирует неосознанные подписки. Таким образом, владельцы карт гарантировано защищены от нежелательных списаний с мобильного счёта.

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

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


    1. sergeich_gs Автор
      05.09.2018 18:42

      Да. А ещё «МегаФон» предлагает бесплатную (неужели?) услугу запрета подобных подписок. Но только на три месяца, потом придётся переподключать.


      1. Barbaresk
        05.09.2018 23:58
        +3

        Ну Билайн в этом плане не лучше. На днях моя бабушка пожаловалась, что у неё большие расходы на связь. Оказывается, они полгода назад подключили пенсионеру без его ведома услугу стоимостью 5р/сутки и более полугода крали деньги. Итого украли около 1к рублей у пенсионера. Возвращать отказались, те ещё подонки.


        1. Newcss
          06.09.2018 19:20

          Ясное дело откажутся. Звоните по телефону в билайн и требуете чтобы оформили претензию за незаконное списание денежных средств поставщиком услуг. Говорите что поставщик мошенник (что на самом деле есть правда). Услуга подключена незаконно и подтверждения (ответным СМС) не было. Следующим шагом просите чтобы активировали КОНТЕНТНЫЙ СЧЕТ (любые СМС на короткие номера, звонки на платные номера, платные услуги сторонних операторов оплачиваются с контентного счета, счет не может уйти в минус). По итогам разбирательства Вам вернут изъятые деньги. Уже раза 3 звонил, Бабушке 3 раза подрят подключали, сначала уроки Английского языка, затем Уроки Русского языка и после Гороскоп… После подключения контентного счета проблемы все пропали.


      1. tka4ev
        06.09.2018 04:21

        Контентный счёт подключить. Правда про него у них вообще нигде не найдёшь.


      1. zerg59
        06.09.2018 09:37

        A у Теле2 (ответ службы поддержки):

        Услуга «Отказ от web-подписок», которая подключена на Вашем номере не блокирует брендированные подписки Tele2.


        1. sergeich_gs Автор
          06.09.2018 09:58
          +1

          На самом деле это значит следующее?

          Защита от воровства нашими соучастниками не защищает Вас от воровства лично нами.


        1. achekalin
          06.09.2018 15:12

          То же и у Би. Их собственные разводильные-на-деньги сайты и их рекламу они из всех фильтров исключают.


      1. roscomtheend
        06.09.2018 09:49

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


      1. NoRegrets
        06.09.2018 14:40
        +1

        Вот этими вещами и должно заниматься государство в лице Роскомнадзора или Роспотребнадзора. Такие услуги должны быть у всех операторов и должны быть бесплатными, бессрочными и подключаться по-умолчанию. А все контентные услуги должны использовать отдельный контентный счет, вне зависимости от того, кто оказывает услуги — третье лицо или сам оператор. Но кому это нужно в насквозь коррумпированном государстве.


        1. sergeich_gs Автор
          06.09.2018 15:10

          Но, к сожалению, Роскомнадзор, очевидно, в доле с ОПСОСами.


      1. domix32
        06.09.2018 15:12

        Есть еще т.н. «Контентный счет» разделяющий счета подписки и прочих мобильных услуг. Существует в том или ином виде у всех операторов и подключается через поддержку


  1. Sabubu
    05.09.2018 17:27

    А любопытно, не проще ли было написать браузерное расширение, которое бы через браузер загружало страницу с объявлениями и само нажимало на кнопки блокировки? Просто есть ощущение, что это может быть проще и так вам не надо беспокоиться об авторизации и всех этих токенах. Ну и установить его еще легче, чем PHP-код на сервер.


    1. sergeich_gs Автор
      05.09.2018 18:48

      Для меня — нет. Вообще не умею творить браузерные расширения.

      К тому же, у этого метода есть недостаток — для работы нужен браузер с расширением, а php-код по cron запускается постоянно, работает сам на сервере и его не видно. Можно изредка заходить глянуть отчёт, да настройки подкрутить.


      1. inoyakaigor
        06.09.2018 13:28

        А не проще ли поставить uBlock или т.п. и не видеть рекламы вообще*?
        ________
        Только там где не позаботились о борьбе с блокером


        1. sergeich_gs Автор
          06.09.2018 13:47

          Это вообще не о том. Тема о блокировании определённой рекламы на своём сайте для посетителей.


  1. ukt
    05.09.2018 18:29

    Но не у всех пользователей наших сайтов есть «Мегафонкарты» и аналоги других ОПСОСов.
    Поэтому, господа, защищайте посетителей своих ресурсов от нежелательных списаний самостоятельно!

    Штука такая, контентный счет. С нулевым балансом или нужным балансом на момент совершения действия.
    Есть у всех операторов.


    1. sergeich_gs Автор
      05.09.2018 18:54

      Да, в курсе.
      Но пока человек не столкнулся с проблемой он, даже увидев, что есть какая-то услуга «контентный счет» даже не подумает зачем оно надо и что это может понадобиться ему тоже.

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

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


    1. sha4
      05.09.2018 19:29

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


      1. roscomtheend
        06.09.2018 09:55

        Операторы ввели для КС удобные USSD, чтобы легко подключить, а потом подумали «опа, нас же не обязали делать удобно» и сделали через заявление в офисе. По крайней мере Мегафон.


        1. sergeich_gs Автор
          06.09.2018 12:04

          Кстати, однажды долго и упорно общался с поддержкой Мегафона в чате — изначально подключение такого запрета возможно было «в офисе с паспортом». К концу диалога уже можно было и через чат оформить.


          1. mixaly4
            06.09.2018 15:17

            Чем Вы их убедили?


            1. sergeich_gs Автор
              06.09.2018 18:53

              Уже не помню аргументов.
              Выносил мозг своим негодованием на тему того, что воровать — пожалуйста, а как перестать — беги в офис.


    1. dartraiden
      05.09.2018 19:43

      Если бы ещё с этого счёта списывались деньги за подписки самого оператора… У некоторых (а может и у всех) операторов подписки делятся на собственные и партнёрские. Деньги за собственные списываются с основного счёта.


      1. vis_inet
        05.09.2018 19:52

        Точно?
        Какой тогда смысл в таком счёте?..


        1. dartraiden
          05.09.2018 19:58

          Для оператора — никакого, они же не по своей воле его сделали, а по требованию законодательства. Такую вот нашли в законе лазейку.


    1. zirix
      05.09.2018 21:33
      +1

      Все эти подписки и прочие списания на грани легальности и оператор всегда возвращает деньги если клиента послать не вышло.

      Если деньги списали звоните оператору, требуйте оформить претензию и требуйте возврата денег. Через пару дней вернут.

      На счет контентного счета, там тоже присутствуют попытки надуть клиента. У Билайна он подключается с автоматическим пополнением с основного счета. В итоге деньги все равно списывают.
      Без автопополнения подключается так (Билайн): *110*5062# (https://moskva.beeline.ru/customers/products/mobile/services/details/kontrol-rashodov/)


  1. MOPOH
    05.09.2018 22:23

    Или просто использовать впн, чтобы скрыть свой номер.


    1. Fedcomp
      06.09.2018 10:39

      https?


      1. sergeich_gs Автор
        06.09.2018 12:01
        +1

        Не, это не спасёт.
        Это спасает от встраивания ОПСОСами своей (кстати, весьма назойливой) рекламы в наши сайты.


  1. Zmiy666
    06.09.2018 03:47

    хм… почему бы не сделать блокировку кнопок с размерами более определенного? Ведь ясно же, что большинство разводов — воткнуть кнопку во весь экран или хотя-бы во весь блок, что автоматически означает ее нажатие пользователем, а следовательно мошенничество.


    1. sergeich_gs Автор
      06.09.2018 09:02
      +1

      Да потому что операторам эти разводы в первую очередь выгодны и всё они знают и понимают.


  1. Syzd
    06.09.2018 07:42
    +1

    Все проще.
    Устанавливайте запрет подписок хоть и на три месяца.

    Ну и еще
    «Добавь эти строки в файл hosts на винде и на андроиде, и подписка будет невозможна физически. Я уже не раз налетал на подписки мегафоновские. Они конечно деньги возвращают, но это не делает им чести.
    127.0.0.1 wap.megafonpro.ru
    127.0.0.1 podpiskipro.ru
    127.0.0.1 www.podpiskipro.ru
    127.0.0.1 megafonpro.ru
    127.0.0.1 www.megafonpro.ru
    127.0.0.1 trava.ru
    127.0.0.1 www.trava.ru
    127.0.0.1 www1.vk.com
    »
    Любое оформление подписки на любом сайте это все равно переход на
    podpiskipro.ru. Нет возможности перехода — нет подписки.


    1. sergeich_gs Автор
      06.09.2018 09:11
      +1

      Я уже не раз налетал на подписки мегафоновские. Они конечно деньги возвращают, но это не делает им чести.

      Совсем не делает. Ведь в таком случае вероятность, что кто-то на них в суд подаст сводится практически к нулю, так как для суда нужно подтверждение попытки неудачного досудебного урегулирования разногласий.
      Кто-то из абонентов обнаружил воровство — быстренько ему всё вернуть. Как правило, такой абонент успокаивается, а осадочек не настолько сильный, чтобы думать как же этих мошенников привлечь к ответственности.
      Таким образом они себе строят светлое «отжимное» будущее на долгие годы.


      1. tyomitch
        06.09.2018 12:18

        Такой подход ("--Вышло двести долларов. --Это как же так вышло? --Ну значит, не вышло...") популярен во всех странах и во всех отраслях бизнеса, и абсолютно законен.
        Единственный способ с ним бороться — повышать культуру населения, чтобы привыкали перепроверять все списания и немедленно жаловаться на необоснованные.
        Ваш же подход, наоборот, ориентируется на то, что за каждым пользователем должна следить добрая нянечка, чтобы не дай бог не попал в беду. Такие пользователи никогда не приучатся постоять за себя сами.


        1. sergeich_gs Автор
          06.09.2018 12:27

          С подходом подсовывания в оплату «прокатило» тоже далеко не все научатся, они просто будут выгодными «лохами», не у всех людей хватит навыков общения с техникой и современным миром, чтобы разобраться. Реальный пример — родители. Они спрашивают куда деньги деваются, ведь абонентская плата такая, а на счёт положил уже в 1,5 — 2 раза больше. Надо за них звонить в поддержку, или в личный кабинет заходить и выяснять. Потом объяснять ОПСОСу, что это мы не подключали.


          1. Newcss
            06.09.2018 20:09

            Сначала я тоже долго ругался с ОПСОСом, с мегафоном, билайном… Включал разные опции, запрета, но через пару месяцев история повторялась. И однажды один мудрый специалист из тех.поддержки предложил — а давайте подключим контентный счет, и на этом мои проблемы завершились. Уже больше года я вообще не знаю что такое автоматическое подключение платной подписки), и Вам советую!


  1. sergeich_gs Автор
    06.09.2018 09:15
    +1

    Господа, вы уже написали много разных опсособов по защите от непредвиденных подписок.
    Все они годны для защиты себя.
    А как защитить посетителя своего ресурса?


    1. sergeich_gs Автор
      06.09.2018 09:40

      *способов


  1. Drammm
    06.09.2018 10:56

    sergeich_gs, а можно вносить правки в статью на Хабре?
    Мне кажется вы не правильно расставили акциенты…
    Нужно было сделать заголовок такого плана:

    «Как Google позволяет обворовывать себя и вебмастеров!»

    и в самой статье в начале сделать четкий акцент что проблеме уже более 2 лет!!!
    решается она за 5 минут:
    1.введение стоп слов
    2. недопущением показа объяв на сайте прежде чем они окажутся в ЦПО

    добавить в теги «воровство» «google»

    Хабр это уважаемое СМИ и шумиха тут нам бы очень помогла. А так вы углубились в то как вы кодили ПХП, но главная то проблема в другом.

    Для тех кто не в курсе — УЖЕ БОЛЕЕ 2 лет в Гугле можно рекламировать различный шлак типа запрещенной рекламы казино, «Умер Якубович, Билан… любая знаменитось», в плоть до совсем чернухи — про стояки и потенцию, в общем ужас!


    1. sergeich_gs Автор
      06.09.2018 12:17
      +1

      Править можно.
      Заголовок менять не хочу. Я б так не сказал, как в предложенном варианте. Google здесь лишь инструмент для привлечения «лохов» мелкими шалунишками — арбитражниками, через организованные группировки — партнёрки wap-click. А те, без которых всё это было бы невозможно, кто больше всех с этого имеет — наши операторы мобильной связи.

      Если менять акценты, статья уже получится более жалобная что ли, а так людям читать интереснее. Ну и чтоб это не было бесцельно слегка затронул зачем это всё нужно.

      Уверен, что со стороны Google не всё так просто, не 5 минут. Тем не менее, очевидно, что на протяжении более четырёх (а не двух) лет им нет дела до нашей (не их) проблемы.


    1. tyomitch
      06.09.2018 12:25

      различный шлак типа запрещенной рекламы казино

      FWIW, деятельность казино в РФ запрещена, а вот реклама легальных казино (находящихся вне РФ) — не запрещена.


      1. sergeich_gs Автор
        06.09.2018 12:53
        +1

        Интересно, почему тогда ФАС считает, что Google распространяет незаконную рекламу казино?
        2014, 2018.


        1. tyomitch
          06.09.2018 13:06

          Логика решений ФАС не всегда ясна; из этих их бумаг получается, что съездить в Амстердам курнуть травы — законно, а повесить рекламу «съезди в Амстердам курнуть травы» — незаконно?


          1. sergeich_gs Автор
            06.09.2018 13:52

            Съездить курнуть — в зависимости от законов страны, где Вы курнуть решили.
            В Амстердаме повесить призыв — в зависимости от законов страны.
            В РФ повесить такой плакат — КоАП РФ Статья 6.13 «Пропаганда наркотических средств...».
            Только причём здесь Амстердам и «курнуть»?


  1. usbstor
    06.09.2018 12:19

    у МТС есть услуга «запрет контента».


  1. stratosmi
    06.09.2018 12:27

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


    Разве не на Dart уже 2 года как?
    news.dartlang.org/2016/10/google-adsense-angular-dart.html


    1. sergeich_gs Автор
      06.09.2018 12:32

      Верно. Не знал.
      А AngularDart разве не на Javascript?


      1. stratosmi
        06.09.2018 12:43

        А AngularDart разве не на Javascript?


        Нет.
        Angular до недавнего времени был на Typescript, и просто транспилировался в Javascript и Dart.
        Но уже года 2 как — версия Angular на Dart вообще отдельная.
        «Чтобы использовать все фенечки Dart»

        news.dartlang.org/2016/07/angulardart-is-going-all-dart.html

        Таким образом, если вы смотрите боевую выкладку AdSense — то вы видите результат компиляции Dart в виде практически обфусцированного Javascript (это и понятно, в браузере-то рядового пользователя только Javascript и возможен)


        1. sergeich_gs Автор
          06.09.2018 12:55

          Благодарю. Поправлю в статье.


  1. jumpordie
    06.09.2018 12:33

    Неделю назад Билайн подписали с номера 9855 на 30 руб в сутки. Хорошо что сразу увидел — отключил это. Разбирался с поддержкой — результата 0, деньги не вернули, естественно.



  1. eugene85
    06.09.2018 12:33

    у меня у мамы обычный кнопочный телефон. оператор мегафон. периодически подключаются эти услуги. после звонка в мегафон все деньги возвращают. в последний раз денег наворовали на 3000.


    1. stratosmi
      06.09.2018 12:47
      +1

      Уже давным-давно в РФ на законодательном уровне ввели разделение на обычный сотовый и специальный контентный счет для услуг.
      Единственное что требуется от абонента — высказать желание, что нужно разделить оба счета.
      Разделяете на 2 счета и ничего не кладете на контентный…


      1. sergeich_gs Автор
        06.09.2018 12:58
        +1

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


      1. jumpordie
        06.09.2018 13:44

        проблема в том, что предлагают это разделение уже после фактов платных подписок, а не делают это по умолчанию…
        Сегодня вот опять (только с другого короткого номера пытались подписать (средства не списали, но услуга висела в подключенных).
        Билайн, опомнись, или разойдёмся!


      1. Barbaresk
        06.09.2018 17:19

        Есть подписки на услуги от самого билайна — «Мои смс» и т.п. Подлючаются они к основному счёту и деньги снимают именного с него. Так что не поможет.


  1. rainver
    06.09.2018 12:56

    на билайне было — баланс подходил к концу, нужно было пополнять, пришла смс от оператора, что-то типа «пополните счёт на 400 р. (а могли бы ведь сделать и меньше, да?) и выше и получите бесплатную подписку на блаблабла сроком на месяц». Нормально, да? Пополнил баланс выше определённой суммы — подписался…


    1. Barbaresk
      06.09.2018 17:17

      Я выше уже отписался — билайн таким способом украл около 1000 рублей у моей пожилой бабушки. Тоже похожие условия были, а она даже смс не умеет читать и при очередном внесении средств подключила услугу. За пол года накаполо по 5р/сутки около 1000р. Возвращать средства, естественно, не стали. Поменял бы уже давно опсоса, но у всех остальных такая же фигня. Был корпоративный мегафон, так за год использования мне трижды подключались подписки на какую-то хрень по 20р/день. Хорошо хоть сразу же замечал.


    1. Vilgelm
      07.09.2018 06:10

      Это странно, выгоднее было бы «пополните счет менее чем на 400 руб и получите подписку». Во-первых, больше охват, во-вторых, стимулировали бы пополнять счет на большие суммы.


      1. sergeich_gs Автор
        07.09.2018 08:59

        А если сумма большая, то можно больше снять, прежде чем человек заметит.