解析智能投票网站和CEC网站上的新API

图片



2020年9月13日,在俄罗斯举行了一个投票日。在某些地区,反对派使用了“智能投票”策略,据此,反对派有主意的选民会投票选出最有可能击败当局代表的候选人。



连续第二年,选择“智能投票”候选人的过程引起了有关其透明度主题的讨​​论。另外,我个人对总结独立分析师可能面临的战略的困难感到困惑。UMG的组织者没有发布该战略的详细结果,而只是显示了多少名反对派候选人进入了区域议会的图表



“智能投票”网站上您无法通过指定城市和地区来获得受支持候选人的列表。如果有人想收集该地区的数据,他将不得不做为每个地区选择地址的单调工作。



在任何情况下,我都不会责怪UMG网站的开发人员,它具有实施投票策略所需的所有功能。但是由于事实上在2019年没有人参与收集和发布有关UMG结果的详细数据(除了莫斯科大选),在这些选举中,我决定主动采取主动。



结果是这样的汇总表在本文中,我将告诉您如何获取给定的数据集,如何从Smart Voting网站和新的CEC Web服务中收集信息



图片



智能投票网站



首先,让我们看看我们可以从Smart Voting网站中提取哪些数据。在网站的主页上,有一个用于输入用户注册地址的字段。输入字符串时,将以以下格式显示建议的地址列表:



图片




当选择建议的地址之一时,我们将进入所选地址所附加的投票站页面:



图片


该页面列出了在此区域进行的竞选活动。对于每个广告系列,都有一个供您投票的候选人列表:



图片


在这种情况下,我们看到了州长的选举,UMG没有为此选举反对派候选人。这是由于州长选举分两轮进行,而选民在第一轮中将投票给哪个反对派候选人并不重要。

我们还同时看到了三名候选人,他们被提议在市议会的选举中投票。这是由于索契的选举由多名成员组成的事实。

在今年参与UMG的所有其他竞选活动中,只有单成员选区。



让我们看一下页面代码,发现所有描述的数据都是以方便的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
}




对于投票站,在UMG网站的数据库中指示相应PEC的编号(编号)及其标识符。Id = 440834与该网页的网址中找到的数字匹配(/候选人/ 440834)。 我们能否知道PEC编号和地区,在UMG网站上计算佣金标识符?我找不到明显的依赖关系,因为标识符分布非常混乱: Sochi,PEC#4512-> id = 440834 Sochi,PEC#4513-> id = 441403 Sochi,PEC#4514-> id = 1781216 如何收集数字反射列表PE在ID页中?遍历并检查从1到2,000,000的所有各种标识符听起来效率极低,其中大多数标识符都无法使用。

















但是,如果我们有地址列表,则可以相对容易地将相关投票站的列表放在一起。当您在初始屏幕上输入字符串时,将从服务器返回合适的地址列表以及相应的佣金标识符:



按地址搜索站点



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


  • 地址-地址,最好采用“主题,城市,街道,房屋”的格式。还希望不使用缩写“ street”,“ d。”,因为服务器上的解析器不能很好地处理它们


示例请求: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个选区。即使在极少数情况下投票站的地址可能与它所属的选区不对应,我还是提出以下假设:通过浏览UMG网站上的PEC地址,您可以收集有关每个选区的信息。



后来,使用此假设,我得以收集几乎所有选区的信息。由于选举委员会数据库中地址格式的异质性,我只能手动选择1100个选区中的10个地址。



在Internet上,您可以找到俄罗斯联邦选举委员会定期更新的数据库,其中包含有关地址甚至PEC组成的信息。但是为了提高数据的相关性和可靠性(也因为我对某个字段的格式不满意),我决定自己收集地址列表,因为事实证明,CEC网站具有为此所需的所有功能。



新的CEC Web服务。API方法



GAS“ Vybory”是1995年开发的自动化系统,旨在准备和进行俄罗斯联邦的选举和全民投票。



如果您曾经对选举活动的进展感兴趣,那么您可能会遇到该网站该网站由SAS系统“选举”的基本信息发布,包括投票计数的进展,甚至在选举结果获得批准之前:



图片



如果更早地检索选举结果数据,可能会使用此网站,在对《宪法》修正案进行投票的日子里,一个验证码突然出现在该网站上。验证码非常持久,当您转到网站的每个页面时都会显示:



图片


正如您自己可以从视觉上进行评估那样,验证码当然非常简单,并且肯定有人已经找到了绕过它的方法。我没有进行机器学习,而是转到CEC网站上的一个新部分,鲜为人知的是:数字服务



图片



该部分仅在对修正案进行投票期间出现,并且包含一些通过HTTP进行的Web服务。请求与内部API通信以从GAS“ Vybory”系统接收数据。 Habr用户已经注意了此功能。让我们更详细地考虑它。



以下是此项目中使用的新API的主要请求的说明:



系统中的每个数据结构均包含一个VRN密钥-实体的唯一标识符,可以是网站,竞选,地区或候选人




PEC信息



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 -PEC标识符


请求示例: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-广告系列ID


请求示例: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-广告系列ID
  • PAGE_NUM-列表页码
  • NUMTVD-县号(可选)


请求示例:http : //cikrf.ru/iservices/sgo-visual-rest/vibory/ 4674220125616 /候选人/?页= 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
   }
}




页面 结构包含页面总数,可用于确定何时到达最后一页(或通过服务器返回的空列表)。






该API包含其他方法,主要是查找有关选举/候选人的更多信息。如有必要,您可以轻松跟踪所需的请求。现在,您可以开始上传数据了。



从CEC网站下载数据



在继续下载必要的数据之前,有必要编制一份我们将在项目中使用的竞选活动清单。事实是,“智能投票”并非在所有地方进行,而是在选举中进行:

— ,

— ,

— ( 200 )

( 4 ).

//


由于这些数据的意义微不足道,我决定不理会国杜马的补选。维基百科上有关“选举日”的文章有助于汇总地方议会的选举清单,因为该清单仅列出了大城市的选举。



找一个朋友(他通过完成必要的体力劳动帮助我实施了这个项目),我请他从经典CEC网站的主页上收集各个竞选活动的URL列表。事实是,URL包含我们需要进一步解析的区域和广告系列标识符。



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


结果,该清单包括43个竞选活动。在单一选举日,各级机构共进行了9000多次单独的竞选活动。



现在,有了选择列表和前面列出的API方法,就可以轻松下载数据。通过编写python脚本,使用requests模块发出常规请求,我以原始JSON格式保存了有关候选人和投票站的数据。



下载有关投票站的信息时要考虑的主要事项:仅从1开始直到服务器返回一个空值,都不可能经过所有可能的数字。事实是该区域中的PEC编号可能会中断,并以以下形式出现:

...#1001-#1016,#1101-#1136,1138 ...

或:

#0 - #700,#900 - #1002,1004 ...

要确定该地区最大PEC数量,而不是做不必要的请求,我收集到的数据如下:我试着上传第一个1000个号码的数据,然后检查是否我+ 1,i + 5,i + 100,i + 500,i + 1000编号对应于任何PEC(在这种情况下,继续下载)。



另外,我建议保留从中下载有关区域数据的PEC编号。事实是返回的数据不包含PEC号,而仅包含形式为“ 100号区选举委员会”的名称。我后来不得不处理的获取原始PEC编号的过程导致了短期的错误和沮丧。事实证明,某些地区PEC名称中的编号具有不同的格式。



例如,在乌德穆尔特共和国, PEC具有以下编号:“第1/01,第1/02号,第1/3号”,在利佩茨克州:“第01-01号,第01-02号,第01-03号”在奥伦堡地区,我遇到了一个真正的异国情调:这是唯一一个以某人命名的选举委员会的地区。例如,以“普斯托维托夫兄弟”命名的第1696号区选举委员会



从“智能投票”站点下载数据



现在,对于收集到的每个PEC地址,我们将从UMG网站下载投票数据。在此之前,值得考虑几个功能(在此过程中我已经学到了):



首先,有必要考虑CEC数据库中的地址具有不同的格式,有时甚至在区域的某些区域中也是如此。我必须删除缩写“ d。”,“ G。”和“ st。”,因为“智能投票”站点根本无法应付此类查询的地址搜索。我还建议从地址中删除邮政编码,以及有时会遇到的前缀“俄罗斯联邦”。



其次,UMG网站对DDoS攻击具有强大的保护作用,即使您以0.3秒的间隔发出一百个请求,您的IP也将被禁止。可以使用一组付费代理,但就我个人而言,我只是使用免费代理以及来自我自己和第三方IP的替代请求。为了不被禁止,两次请求之间的间隔约为0.7秒。结果,下载所有数据花费了大约一天的时间。



使用第一章中的查询,算法如下:



  1. 格式化PEC地址
  2. 我们要求提供合适的地址列表
  3. 我们得到一个包含站点页面ID的列表
  4. 我们通过此标识符检查是否已经下载了有关该网站的数据
  5. 加载该标识符的网站的HTML页面
  6. 我们提取元素“ __NEXT_DATA__”并将数据保存为JSON格式


该页面是使用beautifulsoup4库进行解析的



这个过程并非完美无缺:通常,脚本不会在网站上的该区域中找到十几个投票站,或者在一个PEC的地址上找不到有关完全不同的PEC的信息。



这不是问题,因为对于每个地区,我们只需要在站点上找到至少一个相应的页面即可。



为了验证数据的完整性,我们编写了一个简单的脚本,该脚本检查从UMG站点下载的数据集是否包含有关每个选区的信息。如果缺少某些内容,我们将手动补充数据集。同样,在1,100个地区中,只有不到10个这样的特殊情况。



合并来自UMG和CEC网站的数据



在此阶段,我们收集了一个方便的数据结构,其中按地区列出了有关每个候选人的信息:候选人ID,全名,政党,带有有关UMG是否支持他的信息的标签。



收集的候选数据集的示例
{
    "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. 基于UMG网站上的数据数组,我们为每个地区创建了受支持候选人的列表
  2. 使用CEC网站上的数据数组,我们为每个选区创建了已过滤候选人的过滤列表
  3. 在每个地区中,我们用全名计算对应的候选人-UMG-候选人-CEC


当然,这种简单的算法必须解决许多潜在的问题情况。



首先,在一个选区中,有可能会有名称完全相同的候选人。幸运的是,在5000名候选人中,这种情况仅发生在一个案例中,没有一个候选人获得了UMG的支持。



其次,应考虑到CEC网站的数据库中可能存在错误。最常见的错误:全名中包含换行符和多余的空格。另外,当收集关于投票结果的数据时,存在姓氏中的字母“”被替换为“ e”的情况。



第三,必须考虑数据的相关性。 CEC和UMG网站上的数据已更改和更新,直到周六:一些候选人被撤回/恢复,在某些选区,UMG的支持发生了变化。



为了验证UMG列表,编写了一个简单的脚本,该脚本在每个区域发出一个请求(毕竟,现在我们收集的数据集允许我们唯一地标识每个区域专用的页面),并检查名称是否与我们先前收到的名称匹配。



一个有趣的任务是通过分支机构的名称来识别各方。这一点可以跳过,但是我决定这样做以统一信息。问题在于,来自某一方的候选人在CEC数据库中可能具有不同的姓名。例如,对于KPRF,有40多种选择:



  ()    "   " 
-   ""
  
     "   "
...


这种情况变成一个有趣的解析问题,当有25个批次并且每个区域的拼写几乎都不同时。幸运的是,在我帮助我完成所有手动工作的朋友的帮助下,我们编制了一个关键字列表,通过该关键字列表可以唯一确定候选人的政党。



从CEC网站上载选举结果



收集的数据集足以实现该项目的最初目标-我们为每个选区编制了UMG-2020候选人列表。但是,如果有机会获得选举结果,为什么不使用它呢?






地区选举结果



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


  • CAMPAIGN_VRN-广告系列ID
  • DISTRICT_VRN-地区ID


请求示例: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"
   }
}




如您所见,结果以区域委员会协议的形式返回。每个区域具有不同的协议格式和其中的介绍行数,因此必须对提取的数据进行仔细的验证。






当GAS Vybory开始发布初步结果时,我感到有些失望。事实证明,通过API,您只能获取正式批准的结果的数据。初步结果仍可以在选举委员会的旧网站上查看,但不能通过新的Web服务查看。



一天后,知道了50%的结果,并且到本周结束时,几乎所有选举的结果都被汇总了,一些地区仍然拒绝批准结果。在撰写本文时,已经过去了7天,坦波夫的选举结果尚未获得批准。另外,在某些选区中有重新计票,这就是为什么这些结果也无法通过API获得的原因。



结论:API方法当前不适合迅速接收投票结果。您可能需要等待一个多星期才能批准结果,或者您必须解析选举委员会的旧站点,找到一种绕过验证码的方法。



我已经厌倦了等待1100个选区中的30个选区获得批准,因此我使用编写了一个脚本,该脚本从选举委员会的经典站点下载数据,并要求我为每个请求手动解决验证码。由于请求数量如此之少,手动解决验证码不需要花费很长时间。



结果,我将投票结果的数据收集到以下结构中



县投票结果示例
{
...
"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
    },
...
}




对于每个选区,我将选民总数保存在列表中(以计算投票率),有效和无效的选票数量。该结构包含一个词典:候选人标识符->他键入的票数。



公布UMG-2020的结果



首先,我在GitHub上以JSON格式发布了收集的数据数据将被更新,直到所有地区的结果得到验证。



其次,为了吸引对该项目的关注,我决定生成一个Google Spreadsheet,以方便的形式包含所有收集的数据以进行可视化分析。



我将不赘述,不会出现任何困难(除了研究Google Sheets API)。我推荐这篇文章,其中详细介绍了如何与Python中的Google Sheets API进行交互。



图片



结果,我们得到了下表,其中包含:





后记



这个微型项目的想法是在投票日前三天提出的,我个人对如何在最短的时间内学习和实施所有东西感到满意(尽管代码被证明很糟糕)。



我不会就智能投票策略的结果得出任何结论,我只是为选举统计的支持者提供了工具。我敢肯定,其中会有一些人,不久我们将看到有趣的图形和图表的精彩研究。



All Articles