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
- SUBJECT_CODE - RF主题代码
- COMMITTEE_NUM -PEC号
请求示例: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秒。结果,下载所有数据花费了大约一天的时间。
使用第一章中的查询,算法如下:
- 格式化PEC地址
- 我们要求提供合适的地址列表
- 我们得到一个包含站点页面ID的列表
- 我们通过此标识符检查是否已经下载了有关该网站的数据
- 加载该标识符的网站的HTML页面
- 我们提取元素“ __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
}
],
...
}
该算法非常简单:
- 基于UMG网站上的数据数组,我们为每个地区创建了受支持候选人的列表
- 使用CEC网站上的数据数组,我们为每个选区创建了已过滤候选人的过滤列表
- 在每个地区中,我们用全名计算对应的候选人-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进行交互。
结果,我们得到了下表,其中包含:
后记
这个微型项目的想法是在投票日前三天提出的,我个人对如何在最短的时间内学习和实施所有东西感到满意(尽管代码被证明很糟糕)。
我不会就智能投票策略的结果得出任何结论,我只是为选举统计的支持者提供了工具。我敢肯定,其中会有一些人,不久我们将看到有趣的图形和图表的精彩研究。