吐槽一些神奇的政府网站

博客好久没更新了,最近非常忙。到德国交换落下的课必须这学期补,还有论文要写,加上实习,(其实还有每天忍不住玩守望先锋的时间)基本上时间就塞满了。最近实习的主要内容是写爬虫,工作有点无聊,技术上没有啥挑战,但是要处理很多边界值这种情况。也见识了很多让人哭笑不得的网站。

爬的目标很多是政府网站。在我的印象中,政府网站和大学官网是奇葩一般的存在,对了,国有公司的官网也算是吧。这种网站访问量不少,但是用户体验非常糟糕,看了页面的源代码,你都不知道这种网站是怎么写出来的,怎么维护(所以一般不维护吧……)。

比如说,某网站的css全部写在页面内,,<p>标签中穿插着各种<span> <font>,整个页面没有使用一个css class!这个页面的开发维护人员,一定惊为天人!于是这个页面的爬虫,只好写成了这样子……

还有一个神奇的网站,爬虫写好的时候,发现抓不到数据。打开网页一看,数据没了!网页是能打开的,但是没有数据!留下我一脸懵逼不知所措,回家睡了一觉之后发现,又能打开了!于是我发现了规律,上午9点到下午5点是能打开的,就在调度上让这个爬虫在每天的11点运行,并注释:此网站朝九晚五,估计是运行在工作人员自己的电脑上。

又想起来一个,一个公布著作权的网站,列表页打开特别慢,但是详情页能秒开。页数显示,这个网站的列表页有五百多万页,于是我想,这个网站的页数大概是把所有的数据取出来计算一下数量,才会这么慢吧……

抓某个网站的时候,发现数据太多,但是列表页只显示前100条数据。于是按天来抓,竟然也超过了100页。仔细观察了列表页,发现url是长这个样子的:

最后竟然有个length参数,将10改成300,原本每页只显示10条记录,现在变成每页显示300条了……好吧,爽啊……

我有点好奇,既然可以改成300,那么直接用一页就爬回来所有的数据呢?于是将length改成了13000000(数据总量),结果一段时间之后,网站返回504,然后……挂掉了……这可能是我见过最容易DOS的网站了,只要一条命令就能宕机。

大量的政府网站竟然……没有备案!比如说这个:http://119.6.84.165:8085/sfgk/webapp/area/cdsfgk/splc/ktgg.jsp …………这应该算是打脸了吧……

除此之外,这些网站的URL混乱不堪,页面结构杂乱无章,安全方面更不必说了,使用HTTPS的一个都没有,却基本上都会有被时代抛弃的flash。这都是政府的网站,稍微拨点款找个靠谱一点的外包公司,也不至于这样啊。

 

理解Python的with语句和上下文管理器

With语句的语法和原理

基本语法

with是用来操作上下文管理器的。

上下文管理器(Context Manager):支持上下文管理协议的对象,这种对象实现了__enter__()__exit__() 方法。上下文管理器定义执行 with 语句时要建立的运行时上下文,负责执行 with 语句块上下文中的进入与退出操作。通常使用 with 语句调用上下文管理器,也可以通过直接调用其方法来使用。

换句话说,实现了__enter__()__exit__()的就是上下文管理器,就可以使用with语句。

使用with语句,会先执行上下文管理器context_expression的__enter__()方法,如果该方法有返回值,将返回值赋值给as的变量,然后执行with语句块的内容,最后执行with-body

上下文管理器的基本语法:

将一个上下文管理器用try-catch的方式实现:

原理

with的执行过程如下:

首先,执行__enter__(),返回值赋值给value

然后执行withwith-body代码块,如果上一步有value,则使用。

如果with-body执行过程中发生异常,执行__exit__()方法。没有发生异常也会执行__exit__()方法,区别就是exc变量。

如果发生异常退出的,__exit__()如果返回True,则异常被忽略;否则异常被重新抛出,交给with外面的代码处理。如果__exit__()发生了异常,则会取代with-body中的异常抛出。

要处理异常时,不要显示重新抛出异常,即不能重新抛出通过参数传递进来的异常,只需要将返回值设置为False就可以了。之后,上下文管理代码会检测是否 __exit__() 失败来处理异常。

自定义上下文管理器

定义一个实现了__enter__()__exit__()的对象即可。

  • context_manager.__enter__() :进入上下文管理器的运行时上下文,在语句体执行前调用。with 语句将该方法的返回值赋值给 as 子句中的 target,如果指定了 as 子句的话
  • context_manager.__exit__(exc_type, exc_value, exc_traceback) :退出与上下文管理器相关的运行时上下文,返回一个布尔值表示是否对with-body过程中发生的异常进行处理。参数表示引起退出操作的异常,如果退出时没有发生异常,则3个参数都为None。如果发生异常,返回True 表示不处理with-body的异常,否则会在退出该方法后重新抛出异常以由 with 语句之外的代码逻辑进行处理。如果该方法内部产生异常,则会取代由 statement-body中语句产生的异常。要处理异常时,不要显式重新抛出异常,即不能重新抛出通过参数传递进来的异常,只需要将返回值设置为 False就可以了。之后,上下文管理代码会检测是否 __exit__() 失败来处理异常。

contextlib模块

这个模块提供了很多使用的装饰器和函数。

装饰器 contextmanager

装饰一个生成器函数,函数被装饰过后,返回一个上下文管理器对象。

yield之前的语句,作为__enter__()执行,之后的语句作为__exit__()执行。yield作为__enter__()的返回值。

函数 nested

nested可以将多个上下文管理器组织在一起,避免使用嵌套 with 语句。

类似于:

上下文管理器 closing

closing会将包装的对象返回给as,并在with代码块结束的时候调用对象的close()方法:

一个小问题

之前有点疑惑,为什么python内置的open()既没有被@contextmanager装饰器装饰,又不是一个对象,为什么可以使用with语句呢?

还在staskoverflow提问了这个问题。

原来,open()返回的是一个继承自IObase的对象,这个对象也是一个上下文管理器,所以就支持with语句了。

参考资料

  1. Expert-Python
  2. [翻译] 理解Python的With语句
  3. 浅谈 Python 的 with 语句(非常好的资料)
  4. 如何正确理解关键字”with”与上下文管理器
 

关闭Ubuntu每次启动都会显示的错误弹窗

每次装好Ubuntu之后,前几次登录都会出现一个让你报告错误的弹窗:

“System program problem detected”

Do you want to repport the problem now?

无论点“Report problem”还是“Cancel”,下次启动的时候还是会出现。

Ask Ubuntu有人提出了如下解决方案:删掉错误记录。

重启之后,就不会再有这个提示了。

如果不想重启,可以使用下面的命令关闭通知:

 

分享Elasticsearch的故事

在Elasticsearch的官方文档里,有这样一段非常“程序员”的话,很有意思。

许多年前,一个刚结婚的名叫 Shay Banon 的失业开发者,跟着他的妻子去了伦敦,他的妻子在那里学习厨师。 在寻找一个赚钱的工作的时候,为了给他的妻子做一个食谱搜索引擎,他开始使用 Lucene 的一个早期版本。

直接使用 Lucene 是很难的,因此 Shay 开始做一个抽象层,Java 开发者使用它可以很简单的给他们的程序添加搜索功能。 他发布了他的第一个开源项目 Compass。

后来 Shay 获得了一份工作,主要是高性能,分布式环境下的内存数据网格。这个对于高性能,实时,分布式搜索引擎的需求尤为突出, 他决定重写 Compass,把它变为一个独立的服务并取名 Elasticsearch。

第一个公开版本在2010年2月发布,从此以后,Elasticsearch 已经成为了 Github 上最活跃的项目之一,他拥有超过300名 contributors(目前736名 contributors )。 一家公司已经开始围绕 Elasticsearch 提供商业服务,并开发新的特性,但是,Elasticsearch 将永远开源并对所有人可用。

据说,Shay 的妻子还在等着她的食谱搜索引擎…

原文:https://www.elastic.co/guide/cn/elasticsearch/guide/current/intro.html

 

Django的信号机制

Django提供一种信号机制。其实就是观察者模式,又叫发布-订阅(Publish/Subscribe) 。当发生一些动作的时候,发出信号,然后监听了这个信号的函数就会执行。

Django内置了一些信号,比如:

  1. django.db.models.signals.pre_save 在某个Model保存之前调用
  2. django.db.models.signals.post_save 在某个Model保存之后调用
  3. django.db.models.signals.pre_delete 在某个Model删除之前调用
  4. django.db.models.signals.post_delete 在某个Model删除之后调用
  5. django.core.signals.request_started 在建立Http请求时发送
  6. django.core.signals.request_finished 在关闭Http请求时发送

我们要做的,就是注册一个receiver函数。例如,如果要在每次请求完成之后,打印一行字。

可以使用回调的方式注册:

也可以使用装饰器的方式注册,下面这段代码和上面完全是等价的。

receiver回调函数除了可以使用sender之外,还可以使用其他一些参数,比如针对pre_save函数:

  • sender:发送者(如果是pre_save的话,就是model class)
  • instance:实例
  • raw
  • using
  • update_fields

post_save()是一个比较实用函数,可以支持一些联动的更新。而不必让我们每次都写在view里面。比如:有用户提交了退款申请,我们需要把订单的状态修改成“已退款”的状态。就可以使用信号机制,而不必在每处都修改。

当然,这里可以写的更多更周全,例如退款单取消改回状态等。

观察者是非常实用的一个设计模式,Django也支持用户自定义一些信号。