天气控件逆向工程和自动化

2020-06-17 23:21:38

这个条目是我上周一直在做的一个有趣的小项目。这本书没有什么特别或开创性的,但我希望你能从中获得一些乐趣。

我们想要添加到MadBoulder网站的功能之一(项目的简短描述在末尾)是,对于每个涉及登山区的页面,都有一个天气小部件,它提供该地区的天气状况和天气预报的信息。这些信息在计划户外活动时总是很有用的。

添加该功能的两种最明显的方法要么是查询天气API并开发一个很好的布局来显示所提供的信息,要么是在页面中包含一个即用即用的小部件。因为第二个看起来更直截了当,工作量更少,所以我决定走这条路。

在研究了不同的选项之后,我决定使用WeatherWidget.io提供的小部件。不需要注册,也不需要提供电子邮件,我喜欢它的样子,但-不是UI的人,但我认为-它符合网站的感觉,而且很容易设置。

在决定使用哪个小部件之后,就可以为所有受支持的区域生成小部件,并将它们添加到相应的页面。最常见的方法是导航到将从中获取小部件的页面,引入所需位置,生成小部件代码,然后将其复制并粘贴到网页中的所需位置。

这很简单,如果你只需要几个小部件,也不会有太大的麻烦,但是重复这个过程3次之后,就会变得相当乏味和乏味。我们目前有60个受支持的领域,而且名单还在不断增加。我不想手动生成每个小部件。理想情况下,我希望能够从当前区域列表中自动生成小部件,这是我们已经拥有的功能。

为了避免手动生成每个小部件,我决定开发一个能够执行以下操作的脚本:

为此,我们必须检查小部件的代码,并能够自己生成它。

下面,我复制了在将所需位置设置为巴塞罗那并按下获取代码按钮后获得的小部件代码:

可以看到,小部件有两个部分,一个链接(标签)和一个脚本(脚本标签)。

快速检查小部件代码后,很明显,任何位置的小部件代码大部分都是相同的。小部件代码的这部分可以在不做任何修改的情况下复制。

整个脚本部分可以按原样复制,因为它不包含有关我们要显示天气预报的位置的信息。除此之外,链接中的大多数属性也很容易生成。我们只需将该位置替换为小部件引用的当前位置。考虑到这一点,到目前为止,我们的“通用”小部件模板如下所示:

我们只需用适当的值替换标签LOCATION_NAME和LOCATION_FORECTORY_URL,然后,该位置的小部件就可以使用了。我们还可以通过用所需的主题名称替换Data-Theme属性的值来更改主题。

用我们想要的位置替换LOCATION_NAME很简单,但是LOCATION_FORESTORY_URL就不一样了。我们需要了解URL是如何为任何位置构建的,以便能够自己生成它。如果此操作不正确,我们将获得一个404页面,如下所示:

好处是,我们可以通过为不同位置生成小部件来获得任意多的示例URL。有了足够的数据,我们应该能够对URL构建过程进行反向工程。

首先要做的是检查URL的不同部分,然后快速浏览一下,看看我们能得到什么。显示巴塞罗那天气的小部件的URL是:

我们可以看到,URL有两个主要部分。首先,我们有领域和看似是小部件语言的东西:

我们可以预期此部件在任何位置都是相同的。在那之后,我们有一些看起来像是特定于位置的信息:

其中最后一部分是微不足道的,因为它只包含位置的名称。我们可以通过检查获得的另一个位置(西班牙巴伦西亚)的不同语言的url来确认我们当前的假设:

果然,结果不出所料。现在我们只需要弄清楚字母数字序列41d392d17和39d47n0d38是什么意思。我的猜测是,URL的这一部分被用来区分共享相同名称的位置。因此,无论它是如何生成的,我相当肯定它对于每个位置都必须是唯一的,即使它们共享名称。我又测试了几个地点,最后得到了以下列表:

似乎我们有某种模式。唯一出现的字母似乎是d和n,数字的数量似乎不超过3位。从这一点算起,距离较近的地点,如巴塞罗那和维拉萨德马尔,都有非常相似的序列。这让我想到,在每个序列中编码的都是位置的坐标。如果我们检查巴塞罗那的坐标,我们可以证实这个猜测:

对啰!。结果是,坐标四舍五入为两个小数,d用于替换小数点,n用于替换负号。我们已经有了生成有效URL所需的所有信息。通用格式如下所示:

我们已经知道了生成任何位置的小部件代码所需了解的一切。现在是编写小部件生成器的时候了。

现在我们已经获得了构建小部件所需的所有信息,现在可以对其进行编码了。我们计划的结构将是:

位置是从MadBoulder提供的区域数据集中获得的。要获得坐标,理想情况下,我们应该使用WeatherWidget.io正在使用的相同服务,因为如果不这样做,可能会有小的差异,从而导致无效的url。但是,我找不到他们是从哪里得到坐标的。

在测试了不同的服务之后,Open Cage的前向地理编码API提供了最接近的结果,并且可以免费使用。下面是一个样本响应的摘录。

{[.]";结果";:[{[.]";几何";:{";纬度";:51.9526599,";lng";:7.632473}}],";状态";:{";代码";:200,";消息";:";确定";},[.]";TOTAL_RESULTS";:1}。

然后,我们可以定义一个简单的函数来获取给定位置的坐标:

def get_coels(Location):";";";";给定的位置,通过opencagedata API";";";location=location.place(";";,";+";)API_KEY=NONE WITH OPEN(";redentials.txt";,";r";,coding=';utf-8)检索其纬度和经度坐标。)as f:api_key=f.read()query_url=";https://api.opencagedata.com/geocode/v1/json?q={}&;key={}";.format(位置,api_key)inp=urllib.request.urlopen(Query_Url)coord=json.load(Inp)[';Results';][0][';Geometry';]返回坐标。

获得坐标后,我们将纬度和经度四舍五入为两位小数,并通过链接纬度和经度并替换圆点和负号来构建预期的序列:

给定格式为{';lat';:lat,';lng';:lng}格式的一组坐标,格式为weatherwidget.io预期的url坐标格式的定义格式_坐标(坐标):";";";";";";";。格式为:-四舍五入到第二个小数点的坐标-被d减号替换为n-LAT和LNG串联的坐标[';lat';]=round(坐标[';lat';],2)坐标[';lng';]=round(坐标[';lng';],2)lat=str(坐标[';lng';],2)。])如果lat[::-1].find(';.';)==1:lat+=";0";lat=lat=l.place(.#34;,";d";).place(";-";,";n";)lng=str(坐标[';lng';])如果lng[::-1].find(';.';)==1:lng+=";0";lng=lng.place(";.";,";d";).place(";-";,";n";)返回LAT+LNG。

之后,我们准备构建小部件的完整url和链接标记:

A_TAG=";";";类=";天气小部件-io";href=";https://forecast7.com/_LANG_/_COORDS_/_LOCATION_/";数据标签_1=";_LOCATIONPRETTY_";数据标签_2=";天气";数据主题=";纯";>;_LOCATIONPRETTY_天气";";";def get_url_location_name(Location):";";";";将用于搜索坐标的位置名称转换为weatherwidget.io小部件url";";";";Return Location.Split(";,";)[0].LOWER().place(";";,";-";)def get_widget_code(cocords,Pretty。从检索到的信息";";";";位置=get_url_location_name(Pretty_Location)tag=A_tag.place(";_COORDS_";,coods)tag=tag.place(";_LOCATIONPRETTY_";,Pretty_Location)tag=tag.place(";_location_";,COORDS_";,COORDS_";";";";";";";,lang)url=";https://forecast7.com/_LANG_/_COORDS_/_LOCATION_/";url=url.place(";_coords.place(";_location_";,location).place(";_lang_";,lang)返回标签,url。

现在,由于从Open Cage的API获得的坐标可能与WeatherWidget.io使用的坐标有一些不同,所以我们必须确保我们获得的url是有效的。为此,我们可以定义一个简单的函数,该函数向URL发出请求,如果请求成功,则返回True,否则返回False。

def is_url_ok(Url):";";";";";测试weatherwidget.io生成的url是否有效";";";try:req=urllib.request.Request(url,Headers={';User-Agent';:";Magic Browser";})urllib.request.urlopen(Req)除urllib外返回True。

当请求失败时,大多数情况下是由于坐标上的一些微小差异。因此,当url失败时,我们可以尝试用一个启发式过程来修复它,该过程测试以给定坐标为中心的给定范围内的所有可能的坐标。我们可以在每次迭代中将纬度或经度增加或减少0.01,并检查该更改是否修复了url:

def fix_url(COCORS,Pretty_Name,Lang):IF TERTENCE:对于范围内的i(-公差,公差+1):NC=COCORDS[';LAT';]+i/100,对于范围内的j(-TRANCE,TRANSION+1):NCL=COCORDS[';LNG';]+j/100 Formated_COCORDS=FORMAT_COORDIES({';LAT';:NC,';LNG&#。:ncl})tag_code,url=get_widget_code(Formatated_coods,Pretty_Name,lang)if is_url_ok(Url):return tag_code,url return";";,";";

我发现容差值为5在大多数情况下足以找到正确的url。现在我们已经介绍了所有不同的步骤,我们的主要功能可能如下所示:

def main(PRETY_NAME):COCORDS=GET_COORS(PRY_NAME)Formated_COCORDS=Format_COORS(COCORS)tag_code,url=get_widget_code(Formatated_COCORDS,Pretty_Name,";es";)如果不是_url_ok(Url):tag_code,url=fix_url(COCORDS,Pretty_Name,";es&34;)with open(";template.。)作为f:f.write(Tag_code+script)。

其中,在本例中,template.html只是一个用于测试目的的空html文件。在几次执行之后,它看起来如下所示,当在浏览器中打开时,结果是:

和往常一样,您可以在GitHub找到为这个小项目开发的示例代码。

MadBoulder是一个合作项目,旨在成为Boulder Beta的参考库。我们的想法是收集尽可能多的测试版,从最容易的到最难的。

该网站使浏览可用信息和查找所需视频变得更容易。