Як я боровся з крадіжками… за допомогою php


Коли ми платимо щодня за послуги — це покупка послуг.
Коли ми платимо щодня за нічого (часом навіть не підозрюючи про це) — це крадіжки.

Добрий день, читачі !

З чого все почалося

Захотів я щоб крадіжок стало менше, і давай з ним боротися! Але вручну це було дуже важко, довго і малоефективно, тоді й прийшла думка як-то це справа автоматизувати.

Про які «крадіжки» розповідаю я? Про ту, де ми, гуляючи по інтернету, натискаємо на кнопочку «дивитися відео», вантажиться якась сторінка, відео чомусь не відтворюється, ми йдемо і гуляємо далі, а насправді ми «добровільно» підключили собі послугу отримувати щось, що ніхто ніколи не бачив за символічну плату 30 рублів на день з рахунку свого мобільного. У людей це називається wap-click або мобільні підписки, а стільникові оператори придумують різноманітні красиві назви. Ще б пак, не включати ж у список послуг «крадіжки відеокнопки».

Ось тут трохи детальніше. А тут історія про гарний спосіб «заробити».

Описаних випадків не зовсім добровільних підписок багато, цей, наприклад. Неописаних — набагато більше.

Борці теж є:

  • Яндекс раз.
  • Яндекс два.
  • Білайн? (2014) Серйозно?! А що, ціна ж вказана (2016).

 

Що і навіщо було автоматизовано

Пошук і блокування оголошень в панелі видавця 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-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+wOk46bdr0yAu003d"},"5":{"1":82,"2":0,"3":0,"4":"u003cdiv idu003d"ad-parent-id-6A2DE3D206234468F53C743C0EEACD67A59E6C5B62C0371F770419826258CB1AD9591F60"u003eu003c/divu003e","5":"u003cdiv idu003d"ad-parent-id-6A2DE3D206234468F53C743C0EEACD67A59E6C5B62C0371F770419826258CB1AD9591F60"u003eu003c/divu003e","6":"u003cdivu003eu041cu043du043eu0433u043eu0444u043eu0440u043cu0430u0442u043du044bu0435u003cspan idu003d'multi-format-tooltip'u003eu003c/spanu003eu003c/divu003eu003ca classu003d'arc-url-link-ellipsis' targetu003d'_blank' hrefu003d'https://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/' titleu003d'https://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/'u003ehttps://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/u003c/au003e","7":"u003cdiv classu003d'arc-one-by-one-legend'u003eu0422u0438u043f u043eu0431u044au044fu0432u043bu0435u043du0438u044fu003c/divu003eu003cdiv classu003d'arc-one-by-one-data'u003eu041cu043du043eu0433u043eu0444u043eu0440u043cu0430u0442u043du044bu0435u003cspan idu003d'multi-format-tooltip'u003eu003c/spanu003eu003c/divu003eu003cdiv classu003d'arc-one-by-one-legend'u003eu0426u0435u043bu0435u0432u043eu0439 URLu003c/divu003eu003cdiv classu003d'arc-one-by-one-data'u003eu003ca classu003d'arc-url-link-ellipsis' targetu003d'_blank' hrefu003d'https://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/' titleu003d'https://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/'u003ehttps://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/u003c/au003eu003c/divu003eu003cdiv classu003d'arc-one-by-one-legend'u003eu0414u043eu043cu0435u043du044b u0438u0437u0434u0430u0442u0435u043bu0435u0439u003c/divu003eu003cdiv classu003d'arc-one-by-one-data'u003e4aynikam.ruu003c/divu003eu003cdiv classu003d'arc-one-by-one-data'u003eandroidphone.suu003c/divu003eu003cdiv classu003d'arc-one-by-one-data'u003eandroidphones.ruu003c/divu003eu003cdiv classu003d'arc-one-by-one-data'u003efull-repair.comu003c/divu003eu003cdiv classu003d'arc-one-by-one-data'u003ehowgadget.comu003c/divu003eu003cdiv classu003d'arc-one-by-one-legend'u003eu041eu0431u043du0430u0440u0443u0436u0435u043du043du044bu0439 u0440u0435u043au043bu0430u043cu043eu0434u0430u0442u0435u043bu044cu003cspan idu003d'adx-advertiser-tooltip'u003eu003c/spanu003eu003c/divu003eu003cdiv classu003d'arc-one-by-one-data'u003eDNS Shopu003c/divu003e","8":"u003cdivu003eu003cspan classu003d'arc-impression-score high'u003eu0412u042bu0421u041eu041au041eu0415u003c/spanu003e u0447u0438u0441u043bu043e u043fu043eu043au0430u0437u043eu0432u003c/divu003e","9":{"1":"u003ca hrefu003d"https://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/" targetu003d"_blank"u003eu003cimg onerroru003d"this.srcu003d'data:image/gif;base64,RA7'" srcu003d"https://www.google.com/webpagethumbnail?cu003d58u0026su003d400:400u0026ru003d4u0026du003dhttps://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/u0026au003dAIYkKU9ZGGjFTOWtm771MQwgDYxqtlBLCw" borderu003d0 altu003d""u003eu003c/au003e","2":"u003ca hrefu003d"https://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/" targetu003d"_blank"u003eu003cimg onerroru003d"this.srcu003d'data:image/gif;base64,R0AA7'" srcu003d"https://www.google.com/webpagethumbnail?cu003d58u0026su003d400:400u0026ru003d3u0026du003dhttps://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/u0026au003dAIYkKU_CQ2K6v5f11Nk1RXtc87FtmG2B1w" borderu003d0 altu003d""u003eu003c/au003e","3":"u003ca hrefu003d"https://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/" targetu003d"_blank"u003eu003cimg onerroru003d"this.srcu003d'data:image/gif;base64,R0lAA7'" srcu003d"https://www.google.com/webpagethumbnail?cu003d58u0026su003d400:400u0026ru003d6u0026du003dhttps://www.dns-shop.ru/actions/c09a061b-a048-11e8-9547-00155d03330d/u0026au003dAIYkKU_My0a48LAsW-ZKpQX-ATXkMoPEVg" borderu003d0 altu003d""u003eu003c/au003e"},"10":"https://adwords-displayads.googleusercontent.com/da/b/preview.js?clientu003dasfe-arc-external-previewu0026obfuscatedCustomerIdu003d5240877441u0026creativeIdu003d288930210411u0026htmlParentIdu003dad-parent-id-6A2DE3D206234468F53C743C0EEACD67A59E6C5B62C0371F770419826258CB1AD9591F60u0026sigu003dACiVB_yMUjLwDjRO2T-0VAaVuRPt8uLHGQ","13":"https://adwords-displayads.googleusercontent.com/da/b/preview.js?clientu003dasfe-arc-external-previewu0026obfuscatedCustomerIdu003d5240877441u0026creativeIdu003d288930210411u0026htmlParentIdu003dad-parent-id-6A2DE3D206234468F53C743C0EEACD67A59E6C5B62C0371F770419826258CB1AD9591F60u0026showVariationsu003dtrueu0026sigu003dACiVB_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":"AClZvXLE9HJbFYq9TrAsXFgV4YkXsQt9lxp1xwjsb5at5bfbpe4vngou003d"},"2":"u0418u043du0442u0435u0440u043du0435u0442 u0438 u0442u0435u043bu0435u043au043eu043cu043cu0443u043du0438u043au0430u0446u0438u0438","3":"u0422u043eu0432u0430u0440u044b u0438 u0443u0441u043bu0443u0433u0438, u0441u0432u044fu0437u0430u043du043du044bu0435 u0441 u0442u0435u043bu0435u043au043eu043cu043cu0443u043du0438u043au0430u0446u0438u044fu043cu0438, u0432 u0442u043eu043c u0447u0438u0441u043bu0435 u043au0430u0431u0435u043bu044cu043du043eu0435 u0438 u0441u043fu0443u0442u043du0438u043au043eu0432u043eu0435 u043eu0431u0441u043bu0443u0436u0438u0432u0430u043du0438u0435 u0438 u0434u043eu0441u0442u0443u043f u0432 u0418u043du0442u0435u0440u043du0435u0442."},{"1":{"1":"AClZvXKrUJJ3kKBen2scP56BynOtGhf160i1f1llmtbj3b/oh2dUFg8u003d"},"2":"u041cu043eu0431u0438u043bu044cu043du044bu0435 u0442u0435u043bu0435u0444u043eu043du044b","3":"u041cu043eu0431u0438u043bu044cu043du044bu0435 u0438 u0441u043eu0442u043eu0432u044bu0435 u0442u0435u043bu0435u0444u043eu043du044b, u0430 u0442u0430u043au0436u0435 u0441u043eu043fu0443u0442u0441u0442u0432u0443u044eu0449u0430u044f u0438u043du0444u043eu0440u043cu0430u0446u0438u044f, u043du0430u043fu0440u0438u043cu0435u0440 u0442u0435u0445u043du0438u0447u0435u0441u043au0438u0435 u0445u0430u0440u0430u043au0442u0435u0440u0438u0441u0442u0438u043au0438 u0438 u0441u0440u0430u0432u043du0438u0442u0435u043bu044cu043du044bu0439 u0430u043du0430u043bu0438u0437 u0442u043eu0432u0430u0440u043eu0432. u0412 u044du0442u0443 u043au0430u0442u0435u0433u043eu0440u0438u044e u043du0435 u0432u0445u043eu0434u044fu0442 u0430u043au0441u0435u0441u0441u0443u0430u0440u044b u0434u043bu044f u043cu043eu0431u0438u043bu044cu043du044bu0445 u0442u0435u043bu0435u0444u043eu043du043eu0432."},{"1":{"1":"AClZvXL4W+khZ4O9SJiu97cTbTs2+0Wecf1IVNju8ffd4ysIT9PJ7XYu003d"},"2":"u041cu043eu0431u0438u043bu044cu043du044bu0435 u0442u0435u043bu0435u0444u043eu043du044b u0438 u0430u043au0441u0435u0441u0441u0443u0430u0440u044b u0434u043bu044f u043du0438u0445","3":"u041cu043eu0431u0438u043bu044cu043du044bu0435 u0442u0435u043bu0435u0444u043eu043du044b, u0430 u0442u0430u043au0436u0435 u0441u043eu043fu0443u0442u0441u0442u0432u0443u044eu0449u0438u0435 u0430u043au0441u0435u0441u0441u0443u0430u0440u044b u0438 u0430u043fu043fu0430u0440u0430u0442u043du043eu0435 u043eu0431u0435u0441u043fu0435u0447u0435u043du0438u0435, u043du0430u043fu0440u0438u043cu0435u0440 u0447u0435u0445u043bu044b, u043cu043eu043du043eu043fu043eu0434u044b u0434u043bu044f u0441u0435u043bu0444u0438, u0437u0430u0449u0438u0442u043du044bu0435 u044du043au0440u0430u043du044b u0438 u0437u0430u0440u044fu0434u043du044bu0435 u0443u0441u0442u0440u043eu0439u0441u0442u0432u0430."},{"1":{"1":"AClZvXLQ3gPoVwjQbokDpB3+nni4xURwH5+YlnwkqjYtUowjhiKvk8Qu003d"},"2":"u041fu041a u0438 u0431u044bu0442u043eu0432u0430u044f u044du043bu0435u043au0442u0440u043eu043du0438u043au0430","3":"u0422u043eu0432u0430u0440u044b, u0443u0441u043bu0443u0433u0438 u0438 u0438u043du0444u043eu0440u043cu0430u0446u0438u044f, u0441u0432u044fu0437u0430u043du043du044bu0435 u0441 u043au043eu043cu043fu044cu044eu0442u0435u0440u0430u043cu0438 u0438 u0431u044bu0442u043eu0432u043eu0439 u044du043bu0435u043au0442u0440u043eu043du0438u043au043eu0439."},{"1":{"1":"AClZvXLKYOGgOROaa32IUxU15jP89AtTm4dv24wks+daMhqJMTNmeSYu003d"},"2":"u0422u0435u043bu0435u0444u043eu043du0438u044f","3":"u0422u043eu0432u0430u0440u044b, u0443u0441u043bu0443u0433u0438, u0430 u0442u0430u043au0436u0435 u0438u043du0444u043eu0440u043cu0430u0446u0438u043eu043du043du044bu0435 u0438 u0434u0440u0443u0433u0438u0435 u0440u0435u0441u0443u0440u0441u044b, u0441u0432u044fu0437u0430u043du043du044bu0435 u0441 u0442u0435u043bu0435u0444u043eu043du0438u0435u0439 u0438 u0433u043eu043bu043eu0441u043eu0432u043eu0439 u0441u0432u044fu0437u044cu044e."}]},"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+qOQAAAAAnMJaYSAFQAFoLCS8wxxaTatL1eajgp7737gyu003d","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='дані: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='дані: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='дані: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': 'uk','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': 'x7bx221x22:x5b5,25,22,8,27,32,43,44,45,48,49,5,25,22,8,27,32,43,44,45,48,49,29,46x5dx7d','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/e893nEVz3CmF68pphNp71kjJtvwBS2Jtswhfc81ys51gewu003du003d"}}},"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/e893nEVz3CmF68pphNp71kjJtvwBS2Jtswhfc81ys51gewu003du003d"}}},"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».

Отримання конкретного оголошення — все просто, беремо з попереднього відповіді посилання і викачуємо оголошення. Тут вже ніякого захисту, доступ вільний для всіх.

Подробиці Посилання на оголошення. Будемо її шукати в попередній відповіді, де ми отримали багато-багато кілобайт тексту у відповідь на запит оголошень.

Читайте також  Custom Google Search, View

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

{"result":{"1":[{"1":0,"3":0,"4":{"1":"AClZvXJ2t4wiEZ/VZ0i54m0Qtqpi2DTqkI1kaPMTRi4LnsQn0ir5k1xblfps1xmjv7ko4a6qx5rctkp7czvjwoy5udswz5jocplgrcoqddt+wOk46bdr0yAu003d"},"5":{"1":82,"2":0,"3":0,"4":"u00GQ","13":"https://adwords-displayads.googleusercontent.com/da/b/preview.js?clientu003dasfe-arc-external-previewu0026obfuscatedCustomerIdu003d5240877441u0026creativeIdu003d288930210411u0026htmlParentIdu003dad-parent-id-6A2DE3D206234468F53C743C0EEACD67A59E6C5B62C0371F770419826258CB1AD9591F60u0026showVariationsu003dtrueu0026sigu003dACiVB_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":"AClZvXLE9HJbFYq9TrAsXFgV4YkXsQt9lxp1xwjsb5at5bfbpe4vngou003d"},"2":"u041/YHgdH4P"}}],"2":0.0,"3":"59917","4":1,"5":"","6":"ClD3Z2nP2P/////1/ff9oPjm7gUu003d","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'] = 'ан';

 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->властивості->{5})
 file_put_contents($GLOBALS['temp_folder'] . 'some_digi_token.txt', $result->
 default->{5}); // Renew digi token
 if (@$result->властивості->{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<код символу з двох символів знаків>).

Уривок з того оголошення, посилання на який є вище:
targetx3d_blank titlex3dx22x22x3ex3cspanх3еКупи Xiaomi Redmi S2 і отримай Redmi 5 x3cbrх3ев подарунок. З 24 по 26 серпня. x3cbrх3еПодробнее на сайті.
Для 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 заблоковані не дарма. Зі ста «чистих» на думку в середньому менше одного «поганого».

Що я називаю «поганими оголошеннями»? Все що веде на мобільні підписки, все що пропонує «завантажити», просто завантажити або «завантажити файл» без будь-якої конкретики взагалі, все, що пропонує «дивитися відео», знову ж таки без будь-яких подробиць, все що веде зовсім не туди, про що зазначено у заголовку та тексті оголошення, будь-які згадки казино в країнах, де це заборонено законом.

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

Стало менше і крадіжок у вигляді неусвідомлених підписок !

Степан Лютий

Обожнюю технології в сучасному світі. Хоча частенько і замислююся над тим, як далеко вони нас заведуть. Не те, щоб я прям і знаюся на ядрах, пікселях, коллайдерах і інших парсеках. Просто приходжу в захват від того, що може в творчому пориві вигадати людський розум.

You may also like...

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *