Парсинг сайта Умного Голосования и новый API на сайте ЦИК

    image

    13 сентября 2020 года в России прошёл единый день голосования. В некоторых регионах оппозицией была применена стратегия «Умного Голосования», заключающаяся в том, что оппозиционно настроенные избиратели голосуют за единого кандидата, имеющего наивысшие шансы победить представителя от властей.

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

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

    Ни в коем случае не упрекаю разработчиков сайта УмГ, он имеет весь требуемый функционал для реализации стратегии голосования. Но в связи с тем, что в 2019 году никто не занимался сбором и публикацией подробных данных по итогам УмГ (вне московских выборов), на этих выборах я решил взять инициативу в свои руки.

    В итоге получилась вот такая сводная таблица. В данной статье я расскажу, как был получен приведённый набор данных, как собиралась информация с сайтов Умного Голосования и нового веб-сервиса ЦИК.

    image

    Сайт Умного Голосования


    Для начала посмотрим, какие данные мы можем извлечь с сайта «Умного Голосования». На главной странице сайта есть поле для ввода адреса регистрации пользователя. При вводе строки появляется список предложенных адресов в следующем формате:

    image


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

    image

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

    image

    В данном случае мы видим выборы губернатора, для которых УмГ не указало кандидата от оппозиции. Связанно это с тем, что выборы губернаторов проходят в два тура и не имеет значения, за кого из оппозиционных кандидатов проголосуют избиратели в первом туре.
    Также мы видим сразу трёх кандидатов, за которых предлагают проголосовать на выборах в городской парламент. Связанно это с тем, что на выборах в Сочи многомандатные округа.
    На всех остальных выборных кампаниях, задействованных УмГ в этом году, были только одномандатные округа.

    Заглянем в код страницы и обнаружим, что все описанные данные, собраны в удобном JSON-формате. В элементе с id="__NEXT_DATA__", который используется для отрисовки страницы, есть информация об избирательном участке, о соответствующих выборных кампаниях и кандидатах:

    Содержимое __NEXT_DATA__ элемента
    {
       "props":{
          "pageProps":{
             "id":"440384",
             "settings":{
                "id":1,
                "share_photo":"/ganimed-media/share_photo/smartvote_sharepic_1200x628.jpg",
                "video_on_main_page":"https://youtu.be/w8gapDGwWMY",
                "fake_mode":false,
                "title_share":"Объединяемся, чтобы победить Единую Россию",
                "text_share":"Мы разные, но у нас одна политика — мы против монополии «Единой России». Всё остальное — математика.",
                "telegram_bot_link":"https://tlinks.run/smartvotebot",
                "viber_bot_link":"viber://public?id=smartvote",
                "facebook_bot_link":"https://facebook.com/umnoegolosovanie/",
                "alice_link":null,
                "vk_bot_link":null
             },
             "serverData":{
                "commission":{
                   "id":440384,
                   "number":"4317",
                   "address":"354340, Краснодарский край, город Сочи, Адлерский район, улица Богдана Хмельницкого, 24",
                   "descr":"здание средней школы № 49 им. Н.И. Кондратенко",
                   "lat":"43.425923",
                   "lon":"39.920152",
                   "region_id":26,
                   "region_intid":"135637827259064320000372513"
                },
                "campaigns":[
                   {
                      "id":26,
                      "code":"krasnodar-gub-2020",
                      "title":"Выборы губернатора Краснодарского края",
                      "is_regional":true,
                      "ready_date":null,
                      "district":{
                         "id":458,
                         "code":"oik-0",
                         "name":"0",
                         "leaflet":""
                      },
                      "candidates":[
                         {
                            "id":998,
                            "name":"Кондратьева Вениамина Ивановича",
                            "share_image":"/elections-api-media/share/26/998.png",
                            "anticandidate":true,
                            "self_nominated":false,
                            "has_won":false,
                            "has_second_round":false,
                            "party":{
                               "title":"Единая Россия",
                               "antiparty":true
                            }
                         }
                      ]
                   },
                   {
                      "id":28,
                      "code":"krasnodar-sochi-gorduma-2020",
                      "title":"Выборы в городское собрание Сочи",
                      "is_regional":false,
                      "ready_date":null,
                      "district":{
                         "id":526,
                         "code":"oik-2",
                         "name":"2",
                         "leaflet":"/elections-api-media/28/526-1334-1335-5385.pdf"
                      },
                      "candidates":[
                         {
                            "id":1334,
                            "name":"Киров Сабир Рафаилович",
                            "share_image":"/elections-api-media/share/28/1334.png",
                            "anticandidate":false,
                            "self_nominated":true,
                            "has_won":false,
                            "has_second_round":false,
                            "party":null
                         },
                         {
                            "id":1335,
                            "name":"Мукаелян Марине Айковна",
                            "share_image":"/elections-api-media/share/28/1335.png",
                            "anticandidate":false,
                            "self_nominated":true,
                            "has_won":false,
                            "has_second_round":false,
                            "party":null
                         },
                         {
                            "id":5385,
                            "name":"Рябцев Виктор Александрович",
                            "share_image":"/elections-api-media/share/28/5385.png",
                            "anticandidate":false,
                            "self_nominated":false,
                            "has_won":false,
                            "has_second_round":false,
                            "party":{
                               "title":"КПРФ",
                               "antiparty":false
                            }
                         }
                      ]
                   }
                ]
             },
             "error":null,
             "currentUrl":"https://votesmart.appspot.com/candidates/440384"
          }
       },
       "page":"/candidates/[id]",
       "query":{
          "id":"440384"
       },
       "buildId":"U8hjaoxZw8TINu-DU_Ixw",
       "runtimeConfig":{
          "HOST":"https://votesmart.appspot.com"
       },
       "isFallback":false,
       "customServer":true,
       "gip":true
    }
    


    Для избирательного участка указан номер (number) соответствующей УИК и её идентификатор в базе данных сайта УмГ. Id = 440834 соответствует номеру, который содержится в URL-адресе страницы (/candidates/440834).

    Можем ли мы, зная номер УИК и регион, вычислить идентификатор комиссии на сайте УмГ? Я не смог найти очевидную зависимость, так как идентификаторы распределены достаточно хаотично:
    Сочи, УИК №4512 -> id = 440834
    Сочи, УИК №4513 -> id = 441403
    Сочи, УИК №4514 -> id = 1781216

    Каким образом собрать список отражений номеров УИК в id страниц? Перебирать и проверять всевозможные идентификаторы от 1 до 2000000 звучит крайне неэффективно, большинство из этих идентификаторов нерабочие.

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

    Поиск участка по адресу

    https://votesmart.appspot.com/api/v1/cik/addresses?query=ADDRESS

    • ADDRESS — адрес, желательно в формате «Субъект, город, улица, дом». Также желательно без сокращений «ул.», «д.», так как парсер на сервере плохо с ними справляется

    Пример запроса:

    https://votesmart.appspot.com/api/v1/cik/addresses?query=Смоленск ленина

    Результат запроса
    {
       "suggestions":[
          {
             "value":"Смоленская область, город Смоленск, Промышленный район, Ленина улица",
             "data":{
                "fullname":"Смоленская область, город Смоленск, Промышленный район, Ленина улица",
                "level":"7",
                "region_id":69,
                "commission_id":null,
                "intid":"138474570115456000000347353",
                "path":"135637827259064320000359815,135637827259064320000359819,135637827259064320000359820,138474570115456000000347353",
                "snippet":"Смоленская область, город <em>Смоленск</em>, Промышленный район, <em>Ленина</em> улица",
                "score":118.84238
             }
          },
          {
             "value":"Смоленская область, город Смоленск, Ленинский район, Ленина улица, 12А",
             "data":{
                "fullname":"Смоленская область, город Смоленск, Ленинский район, Ленина улица, 12А",
                "level":"8",
                "region_id":69,
                "commission_id":1124357,
                "intid":"135659820348349440000359937",
                "path":"135637827259064320000359815,135637827259064320000359819,135637827259064320000359822,135659820348349440000359708,135659820348349440000359937",
                "snippet":"Смоленская область, город <em>Смоленск</em>, Ленинский район, <em>Ленина</em> улица, 12А",
                "score":115.14931
             }
          },
    ...
       ]
    }


    Где взять список адресов для извлечения данных с сайта? Перебор базы данных всех адресов страны кажется неэффективным решением, ведь для решения нашей задачи нужен лишь один адрес на избирательный округ.

    На каждый избирательный округ приходится в среднем от 2 до 8 участков. Даже не смотря на то, что адрес избирательного участка, в редких случаях, может не соответствовать округу к которому он принадлежит, я выдвинул следующую гипотезу: перебрав адреса УИК на сайте УмГ, можно собрать информацию о каждом округе.

    В дальнейшем, при помощи данной гипотезы мне удалось собралось информацию почти по всем избирательным округам. Из-за неоднородности формата адресов в базе данных избирательных комиссий, лишь адреса 10 округов из 1100 мне пришлось подбирать вручную.

    В интернете можно найти регулярно обновляющуюся базу данных избирательных комиссий РФ, содержащую информацию об адресах и даже составах УИК. Но для большей актуальности и надежности данных (а также по причине того, что меня не устраивал формат определенного поля) я решил собрать список адресов сам, ведь, как оказалось, на сайте ЦИК имеется весь нужный для этого функционал.

    Новый веб-сервиса ЦИК. Методы API


    ГАС «Выборы» — автоматизированная система, разработанная в 1995 году, предназначенная для подготовки и проведения выборов и референдумов в РФ.

    Если вы когда-либо интересовались ходом выборной кампании, то наверняка сталкивались с данным сайтом, на котором публикуется основная информация из системы ГАС «Выборы», в том числе ход подсчёта голосов, ещё до утверждения результатов выборов:

    image

    И если раньше для извлечения результатов выборов датамайнеры пользовались этим сайтом, в дни проведения Голосования по поправкам в Конституцию на сайте внезапно появилась капча. Капча очень настойчивая, появляется при переходе на каждую страницу сайта:

    image

    Как вы сами можете визуально оценить, капча конечно очень простая, и наверняка кто-то уже нашел способы её обходить. Я же, вместо того чтобы заняться машинным обучением, обратился к новому разделу на сайте ЦИК, о котором пока мало кто знает: Цифровые сервисы

    image

    Данный раздел появился как раз во время Голосования по поправкам и содержит в себе несколько веб-сервисов, которые через HTTP-запросы общаются с внутренним API для получения данных из системы ГАС «Выборы». Пользователь Хабра уже обратил внимание на данный функционал. Рассмотрим же его подробнее.

    Далее приведено описание основных запросов нового API, которые использовались в данном проекте:

    Каждая структура данных в системе содержит ключ VRN — уникальный идентификатор объекта, будь то участок, кампания, округ или кандидат.


    Информация об УИК

    http://cikrf.ru/iservices/voter-services/committee/subjcode/SUBJECT_CODE/num/COMMITTEE_NUM


    Пример запроса:

    http://cikrf.ru/iservices/voter-services/committee/subjcode/01/num/2

    Результат запроса
    {
       "vrn":"4014001117979",
       "name":"Участковая избирательная комиссия №2",
       "subjCode":"01",
       "numKsa":"01T001",
       "vid":"5",
       "address":{
          "address":"385200, Республика Адыгея, городской округ Адыгейск, город Адыгейск, проспект имени В.И.Ленина, 16",
          "descr":"здание МБОУ СОШ№1",
          "phone":"8-87772-9-23-72",
          "lat":"44.882893",
          "lon":"39.187187"
       },
       "votingAddress":{
          "address":"385200, Республика Адыгея, городской округ Адыгейск, город Адыгейск, проспект имени В.И.Ленина, 16",
          "descr":"здание МБОУ СОШ№1",
          "phone":"8-87772-9-23-72",
          "lat":"44.882893",
          "lon":"39.187187"
       }
    }
    




    Информация о выборных кампаниях на участке

    http://cikrf.ru/iservices/voter-services/vibory/committee/COMMITTEE_VRN

    • COMMITTEE_VRN — идентификатор УИК

    Пример запроса:

    http://cikrf.ru/iservices/voter-services/vibory/committee/4544028162533

    Результат запроса
    [
       {
          "vrn":"100100163596966",
          "date":"2020-07-01",
          "name":"Общероссийское голосование по вопросу одобрения изменений в Конституцию Российской Федерации",
          "subjCode":"0",
          "pronetvd":null,
          "vidvibref":"0"
       },
       {
          "vrn":"25420001876696",
          "date":"2020-09-13",
          "name":"Выборы депутатов Законодательного Собрания Новосибирской области седьмого созыва",
          "subjCode":"54",
          "pronetvd":"0",
          "vidvibref":"2"
       },
       {
          "vrn":"4544220183446",
          "date":"2020-09-13",
          "name":"Выборы депутатов Совета депутатов города Новосибирска седьмого созыва ",
          "subjCode":"54",
          "pronetvd":null,
          "vidvibref":"2"
       }
    ]
    



    Перечень округов выборной кампании

    http://cikrf.ru/iservices/sgo-visual-rest/vibory/CAMPAIGN_VRN/tvd

    • CAMPAIGN_VRN — идентификатор выборной кампании

    Пример запроса:

    http://cikrf.ru/iservices/sgo-visual-rest/vibory/457422069597/tvd

    Результат запроса
    {
       "_embedded":{
          "tvdDtoList":[
             {
                "vrn":457422069601,
                "namtvd":"Муниципальная избирательная комиссия города Орла",
                "namik":"Муниципальная избирательная комиссия города Орла",
                "numtvd":"0",
                "vidtvd":"ROOT",
                "_links":{
                   "results":{
                      "href":"http://cikrf.ru/iservices/sgo-visual-rest/vibory/457422069597/results/457422069601/proportion"
                   }
                }
             },
             {
                "vrn":457422069602,
                "namik":"Окружная избирательная комиссия № 1",
                "numtvd":"1",
                "vidtvd":"OIK",
                "_links":{
                   "results":{
                      "href":"http://cikrf.ru/iservices/sgo-visual-rest/vibory/457422069597/results/457422069602/major"
                   }
                }
             },
             ...
          ]
       },
       "_links":{
          "self":{
             "href":"http://cikrf.ru/iservices/sgo-visual-rest/vibory/457422069597/tvd"
          }
       }
    }
    


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

    Как видите, структура данных содержит и ссылку, по которой можно будет узнать результаты выборов. Ссылка генерируется ещё до публикации итогов голосования.



    Список кандидатов, участвующих в выборной кампании

    http://cikrf.ru/iservices/sgo-visual-rest/vibory/CAMPAIGN_VRN/candidates/?page=PAGE_NUM&numokr=NUMTVD

    • CAMPAIGN_VRN — идентификатор выборной кампании
    • PAGE_NUM — номер страницы списка
    • NUMTVD — номер округа (необязательный параметр)

    Пример запроса:

    http://cikrf.ru/iservices/sgo-visual-rest/vibory/4674220125616/candidates/?page=1&numokr=11

    Результат запроса
    {
       "_embedded":{
          "candidateDtoList":[
             ...
             {
                "index":50,
                "vrn":4674020270868,
                "fio":"Трофименко Владимир Карпович",
                "datroj":"23.04.1964 00:00:00",
                "vidvig":"выдвинут",
                "registr":"зарегистрирован",
                "vrnio":4674220132098,
                "namio":"Региональное отделение Политической партии \"Российская партия пенсионеров за социальную справедливость\" в Смоленской области",
                "numokr":11,
                "tekstat2":"1",
                "_links":{
                   "self":{
                      "href":"http://cikrf.ru/iservices/sgo-visual-rest/vibory/4674220125616/candidates/4674020270868"
                   }
                }
             },
             {
                "index":56,
                "vrn":4674020269642,
                "fio":"Божедомов Евгений Эдуардович",
                "datroj":"15.02.1986 00:00:00",
                "vidvig":"выдвинут",
                "registr":"отказ в регистрации",
                "namio":"Самовыдвижение",
                "numokr":11,
                "tekstat2":"1",
                "_links":{
                   "self":{
                      "href":"http://cikrf.ru/iservices/sgo-visual-rest/vibory/4674220125616/candidates/4674020269642"
                   }
                }
             },
             {
                "index":105,
                "vrn":4674020271181,
                "fio":"Трифоненко Владислав Андреевич",
                "datroj":"15.07.1994 00:00:00",
                "vidvig":"выдвинут",
                "registr":"зарегистрирован",
                "vrnio":4674220134054,
                "namio":"Смоленское городское отделение политической партии \"КОММУНИСТИЧЕСКАЯ ПАРТИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ\"",
                "numokr":11,
                "tekstat2":"1",
                "_links":{
                   "self":{
                      "href":"http://cikrf.ru/iservices/sgo-visual-rest/vibory/4674220125616/candidates/4674020271181"
                   }
                }
             },
             ...
             
          ]
       },
       "_links":{
          "self":{
             "href":"http://cikrf.ru/iservices/sgo-visual-rest/vibory/4674220125616/candidates?page=1&numokr=11"
          }
       },
       "page":{
          "size":20,
          "totalElements":9,
          "totalPages":1,
          "number":1
       }
    }
    


    Структура page содержит общее количество страниц, по ней можно определить когда вы достигните последней страницы (либо по пустому списку, вернувшемуся с сервера).



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

    Выгрузка данных с сайта ЦИК


    Прежде чем приступить к скачиванию нужных данных, нужно было составить список выборных кампаний, которые мы задействуем в проекте. Дело в том, что «Умное Голосование» проходило не везде, а именно на выборах:
    — в законодательные собрания регионов,
    — в городские советы региональных центров,
    — в городские советы крупных городов (с населением больше 200 тысяч человек)
    (А также довыборы в Госдуму по 4 округам).
    // Леонид Волков

    Довыборы в Госдуму я решил проигнорировать, из-за незначительности этих данных. Составить перечень выборов в местные советы помогла статья в Википедии о дне голосования, ведь в ней как раз были перечислены выборы в крупных городах.

    Обратившись к товарищу (который помогал мне реализовывать данный проект, выполняя требуемую ручную работу), я попросил его составить список URL-адресов для соответствующих выборных кампаний, взяв их с главной страницы классического сайта ЦИК. Дело в том, что в URL-адресе содержатся идентификаторы региона и кампании, которые как раз понадобятся нам для дальнейшего парсинга.

    vybory.izbirkom.ru/region/izbirkom?action=show&vrn=21120001136916&
    region=11&prver=1&pronetvd=1

    В итоге, список состоял из 43 выборных кампаний. Всего в Единый день голосования прошло более 9000 отдельных выборных кампаний в органы разного уровня.

    Теперь, имея на руках список выборов и перечисленные ранее методы API, скачать данные не составило никакого труда. Написав скрипт на python, делая обычные запросы про помощи requests модуля, я сохранил данные о кандидатах и избирательных участках в исходном JSON-формате.

    Главное, что стоит учесть при скачивании информации об избирательных участках: недостаточно перебирать всевозможные номера начиная с 1, до тех пор пока сервер не вернет пустое значение. Дело в том, что нумерация УИК в регионе может прерываться, и идти, например, в таком виде:
    ...№1001 — №1016, №1101 — №1136, 1138 ...
    либо:
    №0 — №700, №900 — №1002, 1004...
    Чтобы определить максимальный номер УИК в регионе и не делать лишние запросы, я собирал данные следующим образом: пробовал выгрузить данные по первым 1000 номерам, а затем проверял если i+1,i+5,i+100,i+500,i+1000 номера соответствуют какому-либо УИКу (в случае чего продолжал скачивание).

    Также, рекомендую сохранять номер УИК, по которому вы скачали данные об участке. Дело в том, что возвращаемые данные не содержат номер УИК, а только название в виде: «Участковая Избирательная Комиссия №100». Процесс получения исходного номера УИК, с которым мне позже пришлось столкнуться, привёл к кратковременным багам и фрустрации. Как оказалось, нумерация в названии УИК в некоторых регионах имеет разный формат.

    К примеру, в Удмуртии в названии УИК была следующая нумерация: «№1/01, №1/02, №1/03», в Липецкой области: «№01-01, №01-02, №01-03». В Оренбургской области я столкнулся с настоящей экзотикой: это был единственный регион, где ряд избирательных комиссий были названы в честь кого-то. Например «Участковая избирательная комиссия №1696 имени «Братьев Пустовитовых»

    Выгрузка данных с сайта «Умного Голосования»


    Теперь, по каждому собранному адресу УИК мы собираемся скачать данные о голосовании с сайта УмГ. Перед этим стоит учесть несколько особенностей (о которых я узнал уже в процессе):

    Во первых, надо учесть что адреса в базе данных ЦИК имеют различный формат, порой даже в отдельных областях регионов. Мне пришлось убирать сокращения „д.“, „г.“ и „ул.“, так как сайт «Умного Голосования» совсем не справлялся с поиском адресов по таким запросам. Ещё рекомендую убирать почтовый индекс из адреса, а также, встречающийся иногда префикс „Российская Федерация“.

    Во вторых, сайт УмГ имеет жёсткую защиту от DDoS атак, и даже если вы сделаете сотню запросов с интервалом в 0.3 секунды — ваш IP получит бан. Можно было бы использовать набор из платных прокси, но лично я просто воспользовался бесплатными прокси и чередовал запросы со своего и стороннего IP. Чтоб уж точно не получить бан, между запросами был интервал примерно в 0.7 секунд. В итоге, скачивание всех данных заняло примерно сутки.

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

    1. Форматируем адрес УИК
    2. Делаем запрос на список подходящих адресов
    3. Получаем список, содержащий идентификаторы страниц сайта
    4. Проверяем если уже скачали данные об участке по данному идентификатору
    5. Загружаем HTML-страницу сайта по данному идентификатору
    6. Извлекаем элемент „__NEXT_DATA__“ и сохраняем данные в JSON-формате

    Парсинг страницы происходил при помощи библиотеки beautifulsoup4.

    Данный процесс не безупречен: обычно скрипт не находит на сайте десяток избирательных участков в регионе, либо по адресу одного УИК вы находите информацию о совершенно другом УИК.

    Это не беда, ведь для каждого округа, нам достаточно найти хоть одну соответствующую страницу на сайте.

    Для валидации полноты данных мы пишем простой скрипт, который проверяет если в скачанном с сайта УмГ наборе данных содержится информация о каждом избирательном округе. Если чего-то не хватает — пополняем набор данных вручную. Опять же, таких исключительных ситуаций было менее 10 на 1100 округов.

    Объединение данных с сайтов УмГ и ЦИК


    На данном этапе, мы собираем удобную структуру данных, с информацией о каждом кандидате по округам: идентификатор кандидата, ФИО, партия, метка с информацией о том, поддержан ли он УмГ.

    Пример собранного набора данных о кандидатах
    {
        "33": [
            {
                "name": "Бекенева Любовь Александровна",
                "vrn": 4444032121758,
                "birthdate": "05.05.1958 00:00:00",
                "party": "ЕР",
                "smart_vote": 0
            },
            {
                "name": "Крохичев Павел Александрович",
                "vrn": 4444032122449,
                "birthdate": "16.11.1977 00:00:00",
                "party": "КПРФ",
                "smart_vote": 0
            },
            {
                "name": "Ростовцев Михаил Павлович",
                "vrn": 4444032122782,
                "birthdate": "27.02.1996 00:00:00",
                "party": "ЛДПР",
                "smart_vote": 0
            },
            {
                "name": "Морозов Максим Сергеевич",
                "vrn": 4444032123815,
                "birthdate": "20.11.1991 00:00:00",
                "party": "Яблоко",
                "smart_vote": 1
            },
            {
                "name": "Захарова Алина Сергеевна",
                "vrn": 4444032124060,
                "birthdate": "21.07.1996 00:00:00",
                "party": "КПКР",
                "smart_vote": 0
            },
            {
                "name": "Афанасов Александр Николаевич",
                "vrn": 4444032123597,
                "birthdate": "21.05.1974 00:00:00",
                "party": "СР",
                "smart_vote": 0
            }
        ],
        ...
    }
    


    Алгоритм достаточно прямолинейный:

    1. По массиву данных с сайта УмГ создаем список поддержанных кандидатов для каждого округа
    2. По массиву данных с сайта ЦИК создаем отфильтрованный список допущенных кандидатов для каждого округа
    3. В каждом округе по ФИО вычисляем соответствие Кандидат-УмГ—Кандидат-ЦИК

    Конечно, такой простой алгоритм должен учесть множество потенциальных проблемных ситуаций.

    Во первых, есть шанс что в одном округе будут кандидаты с полностью совпадающими ФИО. Благо, среди 5000 кандидатов, такая ситуация была лишь в одном случае, причём ни один из кандидатов не был поддержан УмГ.

    Во вторых, надо учесть, что в базе данных сайта ЦИКа могут быть ошибки. Самая частая ошибка: переносы строк и лишние пробелы в ФИО. Также, при сборе данных об итогах голосования попадалась ситуация, при которых буква „ё“ в фамилии заменялась на „е“.

    В третьих, надо учитывать актуальность данных. Данные на сайте ЦИКа и УмГ изменялись и обновлялись вплоть до субботы: каких-то кандидатов снимали/восстанавливали, в каких-то округах менялась поддержка УмГ.

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

    Интересной задачей была идентификация партий по названию их отделений. Данный пункт можно было бы пропустить, но я решил заняться этим для унификации информации. Проблема заключается в том, что у кандидатов от одной партии может различаться её название в базе ЦИК. Например, в случае КПРФ встречалось более 40 вариантов:

    Ивановское городское (местное) отделение Политической партии "Коммунистическая партия Российской Федерации" 
    Ямало-Ненецкое ОО ПП "КПРФ"
    ЧОО ПП КПРФ
    КАЛУЖСКОЕ РЕГИОНАЛЬНОЕ ОТДЕЛЕНИЕ политической партии "КОММУНИСТИЧЕСКАЯ ПАРТИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ"
    ...

    Ситуация превращается в интересную задачу по синтаксическому анализу, когда партий 25 штук и почти у каждой разное написание каждом регионе. Благо, про помощи моего товарища, который помогал мне со всей ручной работой, мы составили список ключевых слов, по которым однозначно определяется партия кандидата.

    Выгрузка результатов выборов с сайта ЦИК


    Собранного набора данных хватило для достижения первоначальной цели проекта — мы составили списки кандидатов УМГ-2020 для каждого избирательного округа. Но если есть техническая возможность получить результаты выборов, почему бы не воспользоваться ею?



    Результаты выборов в округе

    http://cikrf.ru/iservices/sgo-visual-rest/vibory/CAMPAIGN_VRN/results/DISTRICT_VRN/major

    • CAMPAIGN_VRN — идентификатор выборной кампании
    • DISTRICT_VRN — идентификатор округа

    Пример запроса:
    http://cikrf.ru/iservices/sgo-visual-rest/vibory/457422069597/results/457422069602/major

    Результат запроса
    {
       "report":{
          "tvd":"",
          "date_sign":"none",
          "vrnvibref":"457422069597",
          "line":[
             {
                "txt":"число избирателей на момент окончания голосования",
                "kolza":"8488",
                "index":"1"
             },
             {
                "txt":"число бюллетеней, полученных участковой комиссией",
                "kolza":"6700",
                "index":"2"
             },
             ...
             {
                "txt":"число недействительных бюллетеней",
                "kolza":"65",
                "index":"9"
             },
             {
                "txt":"число действительных бюллетеней",
                "kolza":"1948",
                "index":"10"
             },
             ...
             {
                "delimetr":"1"
             },
             {
                "txt":"Авдеев Максим Юрьевич",
                "numsved":"1",
                "kolza":"112",
                "index":"11",
                "namio":"ПАРТИЯ ПЕНСИОНЕРОВ в Орловской области",
                "perza":"5.56",
                "numsvreestr":"4574030258379"
             },
             {
                "txt":"Жуков Александр Александрович",
                "numsved":"2",
                "kolza":"186",
                "index":"12",
                "namio":"Орловское региональное отделение Партии СПРАВЕДЛИВАЯ РОССИЯ",
                "perza":"9.24",
                "numsvreestr":"4574030258723"
             },
             {
                "txt":"Жуков Родион Вячеславович",
                "numsved":"3",
                "kolza":"54",
                "index":"13",
                "namio":"Самовыдвижение",
                "perza":"2.68",
                "numsvreestr":"4574030258555"
             },
             ...
          ],
          "data_gol":"13.09.2020 00:00:00",
          "is_uik":"0",
          "type":"423",
          "version":"0",
          "sgo_version":"5.6.0",
          "isplann":"0",
          "podpisano":"1",
          "versions":{
             "ver":{
                "current":"true",
                "content":"0"
             }
          },
          "vibory":"Выборы депутатов Орловского городского Совета народных депутатов шестого созыва",
          "repforms":"1",
          "generation_time":"14.09.2020 07:59:21",
          "nazv":"Результаты выборов по одномандатному (многомандатному) округу",
          "datepodp":"14.09.2020 05:44:00"
       }
    }
    


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



    Когда в ГАС «Выборы» начали публиковать предварительные результаты, я столкнулся с небольшим разочарованием. Оказалось, что через API можно получить данные только по тем результатам, которые официально утвердили. С предварительными результатами всё ещё можно ознакомиться на старом сайте избиркома, но нельзя через новые веб-сервисы.

    Спустя сутки были известны результаты по 50%, а к концу недели были подведены итоги почти всех выборов, некоторые регионы всё ещё отказывались утверждать результаты. На момент написания статьи, прошло уже 7 дней, а результаты выборов в Тамбове всё ещё не утверждены. К тому же, в некоторых округах происходит пересчёт голосов, из-за чего эти результаты также недоступны через API.

    Вывод: методы API на данный момент не подходят для оперативного получения результатов голосования. Вам либо придётся ждать более недели, когда же утвердят результаты, либо придётся парсить старый сайт избиркома, найдя способ обхода капчи.

    Мне же надоело ждать когда в ~30 округах из 1100 утвердят выборы, поэтому я написал скрипт, при помощи selenium библиотеки, который выгружает данные с классического сайта избиркома и просит меня вручную решить капчу при каждом запросе. С таким небольшим числом запросов, вручную решать капчу не занимает много времени.

    В результате, данные об итогах голосования я собрал в следующую структуру:

    Пример результатов голосования в округе
    {
    ...
    "33": {
            "candidate_total": {
                "4444032121758": 880,
                "4444032122449": 236,
                "4444032122782": 143,
                "4444032123597": 152,
                "4444032123815": 149,
                "4444032124060": 72
            },
            "is_final": 1,
            "non_valid_votes": 132,
            "registered_voters": 6928,
            "valid_votes": 1632
        },
    ...
    }
    


    Для каждого округа я сохранил суммарное число избирателей в списках (для подсчёта явки), число действительных и недействительных бюллетеней. В структуре содержится словарь: Идентификатор кандидата -> Набранное им число голосов.

    Публикация итогов УмГ-2020


    Во первых, собранные данные в JSON-формате я опубликовал на GitHub. Данные будут обновляться, пока результаты не утвердят во всех округах.

    Во вторых, для привлечения внимания к проекту, я решил сгенерировать Google Таблицу, в которой, в удобном для визуального анализа виде, приведены все собранные данные.

    Вдаваться в подробности не буду, никаких сложностей (кроме изучения Google Sheets API) возникнуть не должно. Рекомендую данную статью, в которой подробно рассказано взаимодействие с Google Sheets API на Python.

    image

    В итоге получилась такая таблица, в которой собраны:


    Послесловие


    Идея данного мини-проекта возникла за 3 дня до дня голосования и лично я доволен тем, как успел изучить и реализовать всё в кратчайшие сроки (хотя код получился ужасным).

    Я не собираюсь делать какие-либо выводы об итогах стратегии «Умного Голосования», я лишь предоставил инструменты для любителей электоральной статистики. Уверен, среди вас найдутся таковые и скоро мы увидим замечательные исследования, с интересными графиками и диаграммами.

    Комментарии 42

      –52
      Выглядит как самый прекрасный метод покупки голосов избирателей. Заноси чемодан денег, получай поддержку кандидата.

      Людям пора понять, что денежный мотив является главным для ВСЕХ политиков, с обеих сторон.
        +46
        Дайте контакты кто платит, совмещу приятное с полезным.
        А то как дурак на всех выборах голосую против текущих властей с 2008 года, а ни копейки не получил.
          +38
          Да и раз уж пошла такая пьянка, пускай и контакты кукловодов для Беларуси приложат, а то мы тут выходим и выходим, а денег все нет.
            –5
            А зачем вам давать денег если вы добровольно пришли? Деньги нужны чтоб мотивировать тех кто не хочет это делать или для организатора который сможет мотивировать, а он в свою очередь сможет для мотивации использовать деньги или не использовать если можно бесплатно.
            +10
            позвольте я перефразирую: в схеме, когда люди голосуют за того, за кого попросят по СМС, а выбор кандидата происходит не прозрачно (как я понял из статьи, сам не проверял), есть коррупционная возможность.
            Пользовались этой возможностью или нет — другой вопрос.
            Так что если вы хотите, чтоб вам кто-то предложил денег — станьте во главе такой схемы.
              +3
              Я голосую ЗА, но тоже что-то деньги до меня не доходят. Складывается ощущение что кто-то наши и ваши деньги ворует. Поэтому мы ничего не получаем.
                –16
                Почему сразу как дурак, мож позиция у вас такая, денег не брать. И причем тут получение денег избирателями? Деньги заносятся тем, кто обещает голоса -) а уж как там хомячков уговаривают — дело десятое, кто-то вон против Лукашенки кричит, кто-то новичок из горла хлебает. А если серьезно — УГ (интересно, те, кто придумывали название, реально не знали как эта аббревиатура расшифровывается в блогосфере?) не имеет смысла, поскольку ориентировано на кандидатов, которые и так имели все шансы избрания. Как правильно заметили ниже, КПРФ что с УГ, что без УГ имело стабильный процент. Так что это просто очередной медиа проект… такой же яркий как и бесполезный.
                  +2
                  А если серьезно — УГ (интересно, те, кто придумывали название, реально не знали как эта аббревиатура расшифровывается в блогосфере?)

                  Придумайте любое название, и если у него найдётся достаточное количество недоброжелателей, то «правильно» расшифровывающеяся абривеатура или сокращение — дело времени.
                    +3
                    Достаточно вбить в urbandictionary.com любую аббревиатуру латиницей, чтобы убедиться, что у неё есть неприличное значение.
                      +2
                      Да туда можно любое слово вбить из любого дурого словаря и убедиться в том же самом=)
              +4
              Оочень хорошая наработка по парсингу ЦИКовских данных. Голос и прочие социологи сильно зашиваются с этой капчей.
                +3
                Там Григорий Мельконьянц (Голос) сообщил, что они запустили копию сайта ЦИКа без капчи
                notelections.online

                Данная страница построена на основании страницы сайта ЦИК, в ней не изменяются никакие данные выборов, лишь обходится капча и показываются ссылки на старые версии протоколов, пример.
                Работают лишь ссылки по данным ГАС, в шапке — нет
                Автор: Шукшин Иван
                  0
                  Серьёзно ??? Вот не думал! Блин, надо будет мне свой загрузчик в открытый доступ выложить. Я её уже обошел. Единственно опасаюсь, он у меня многопоточный, через халявные прокси-сервера. Боюсь как бы народ сайт не снёс, если толпа ломанется качать в сотню потоков каждый.
                  +18

                  Интересно, что кандидаты УмГ в массе заняли 2 место. Кажется это единственная реальная политическая сила в стране и только голосование на пеньках спасает едро от полного разгрома.

                    +7
                    КПРФ и без умного голосования второе место всегда имела )
                      +1

                      Резонно, тогда для подведения итогов важно смотреть на типичные % голосов кандидатов от КПРФ до поддержки УмГ и после. Похоже это довольно много работы.

                      –8
                      Насколько я понимаю, УГ делает ставку на того кандидата который и так может выиграть. Так что по факту УГ это система падания на хвост более удачным конкурентам. В случае победы можно на ясном глазу заявлять «Это мы с УГ привели депутата Имярек к победе», а в случае неуспеха сказать «Такие взрослые люди. а до сих пор верите в говорящих лошадей» «Не шмогла я, не шмошла».
                      Про закрытость УГ и его определенный инфантилизм я пожалуй просто умолчу. Если уж пошел голосовать, голосуй самостоятельно и отвечай за сделанный выбор. Это твоё решение +1000
                        +1
                        Вот только не падение на хвост удачливым конкурентам, а поддержка этих самых удачливых конкурентов, чтобы им точно хватило голосов для победы над ЕР. Всего в паре слов разница, а как сильно меняется смысл!
                          0
                          Удачливые, точнее имеющие внятную и последовательную политическую и экономическую программу, которую поддерживает существенная часть избирателей, выиграют и без УГ, за счет «сарафанного радио», постоянного участия в жизни избирателей этой политической силы, и вообще присутствия на политическом поле.
                          И умное голосование, это когда ТЫ делаешь выбор САМ, самостоятельно рассмотрев кандидатов, самостоятельно оценив их. И делать это не в день выборов, а гораздо раньше, и еще надо помнить кто и что обещал, и когда планировал сделать, и сделал ли это вообще. Тогда есть шанс что-то изменить своим голосом. А голосование по «темнику» спущенному в смарт, ни чем не отличается от голосования по разнарядке. Плохой он, хороший-ли — неважно, он твой кандидат! И ты должен проголосовать за него, потому что КТО-ТО решил это ЗА ТЕБЯ. «Зачем нам думать? За нас думают вожди!»

                          Поэтому и призываю: думайте САМОСТОЯТЕЛЬНО, решайте САМОСТОЯТЕЛЬНО, голосуйте САМОСТОЯТЕЛЬНО. Делайте ОСОЗНАННЫЙ выбор САМОСТОЯТЕЛЬНО. И принимайте ответственность за свой выбор ОСОЗНАННО и САМОСТОЯТЕЛЬНО.
                          Всего лишь два слова ОСОЗНАННО и САМОСТОЯТЕЛЬНО, а как меняется смысл фразы «Я сделал выбор. Это мой осознанный и самостоятельный выбор».
                            +2
                            имеющие внятную и последовательную политическую и экономическую программу, которую поддерживает существенная часть избирателей

                            --тупо не будут зарегистрированы. Достаточно основания «эксперт посчитал, что ваши подписи ненастоящие».
                              –2
                              Так надо не давать повода чтобы «эксперт посчитал, что ваши подписи ненастоящие»
                                0
                                  –1
                                  Повторюсь: имеющие внятную и последовательную политическую и экономическую программу, которую поддерживает существенная часть избирателей, выиграют и без УГ, за счет «сарафанного радио», постоянного участия в жизни избирателей этой политической силы, и вообще присутствия на политическом поле.
                                  «Репутация зарабатывается так тяжело, а потерять её можно в мгновенье, сказав одно неосторожное слово»
                        +1
                        Идея УмГ именно в том, чтобы голосовать за второе место. Потому что оно имеет наибольшие шансы обогнать первое, а это цель.
                        +8

                        Собрать данные и не провести их анализ — это как покинуть синематограф на середине хорошего фильма

                          +6
                          по-хорошему этим разные люди занимаются. Одни достали данные и подготовили их для дальнейшей обработки, а другие уже могут крутить циферки и проверять гипотезы
                          0

                          Номера УИК имеют определенную структуру. Первая группа цифр (обычно две) — это номер ТИКа в субъекте, вторая группа цифр это номер УИКа относящемуся к данному ТИКу. Обычно они утверждены ЦИКом имеют постоянные номера на протяжении многих лет. Есть еще временные, они создаются на определенные выборы, и вторая группа начинается по моему с номера 90. Например номер 01-01 ТИК номер один в субъекте, 01 — участок в данном тике.
                          Сплошной нумерации УИКов в субъектах не существует. Каждый участок относится к конкретному ТИКу. поэтому бессмысленно искать максимальный уровень УИКа в субъекте, не учитывая ТИКи.

                            +3
                            Данная система применима лишь к некоторым регионам. Везде различается система нумерации, например, если вы изучите нумерацию в Татарстане, то увидите что никакой строгой структуры нет

                            www.tatarstan.vybory.izbirkom.ru/region/tatarstan/?action=ik&vrn=21620001856022
                            1 ТИК Казани: №1 — №41
                            2 ТИК Казани: №42 — №75
                            3 ТИК Казани: №86 — №130, №134
                            5 ТИК Казани: №135 — №181
                            6 ТИК Казани: №261 — №343,№348, №453, №2757
                            7 ТИК Казани: №183, №349 — №458

                            ТИК Агрызского района: №459 — №507

                            ТИК Кайбицкого района: №1592 — №1623

                            и так далее :)

                            Но описанная вами система действительно применяется в некоторых регионах со строгой нумерацией УИКов по ТИКам. Например в Удмуртии и Липецкой области.
                              +1

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

                            –22
                            Небольшой разбор УГ от Шария

                              +7
                              Шарий не сильно от Соловьева отличается по содержательной части.
                                –11
                                Это многие «либералы» (в т.ч. команда Навального) по восприятию аргументированной критики ничем не отличается от Соловьева

                                p.s. Шарий частый гость эхо Москвы
                                  +2
                                  Шарий лжец и обманщик, каких поискать. А еще не умеет анализировать данные, которые тащит у других и с математикой не дружит…
                                    0
                                    Так что, сударь, они сделали ложный донос; кроме того, сказали неправду; во-вторых, оклеветали; в-шестых и последних, оболгали благородную девицу; в-третьих, подтвердили неверные вещи; и, в заключение, они лгуны и мошенники.
                              +16
                              Нужна самая важная и самая интересная статистика:
                              Сколько победивших «независимых» кандидатов вступило в «Единую Россию» в течении месяца после подведения итогов голосования?
                                0
                                потом по этим данным создадим модель которая будет предсказывать вероятность перехода к ЕР
                                  0
                                  Если меньше 100%, то уже лучше, чем было бы без УГ.
                                  0
                                  Немножко занимаюсь компьютерным зрением. Могу предложить решение капчи — t.me/t_bus/340
                                    0
                                    А количество зарегистрированных избирателей в разрезе УИК с сайта ЦИК можно получить?
                                      0

                                      Либо по первой строке протокола УИК, либо в сводной таблице результатов выборов (тоже первая строка)

                                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                    Самое читаемое