爬虫常用的工具

前面写过一篇《如何成为一名爬虫工程师》,这篇博文介绍一些写爬虫的神器。比如查看网络请求,模拟网络请求等。本文不会涉及到编程工具例如vim,tmux等(虽然这些工具我也极力推荐,应该人手一份)。

curl

cURL

curl是在命令行用url语法传输文本的工具。用curl可以模拟任何从浏览器发出的请求,并且是以纯文本的形式,所以任何header、cookies都一目了然。有时候你碰到“为什么在浏览器请求能成功,在代码中就不行”的问题时,用curl验证一下是最靠谱的办法。

curl的用法非常简单,curl + "url"就可以向一个url发送请求,显示服务器响应。-H "header"可以给请求加上headers。-i参数可以打印出响应头,大写的-I只打印响应头。-L参数开启301,302跳转等。学习会这些基本就可以上手了,希望详细的了解curl可以阅读《everything curl》这本书(免费书籍:PDF,在线阅读)

一个请求一般带有很多headers,一个一个输入太费劲了,型号,我们可以使用chrome的copy as curl功能。

此命令会将一个网络请求转换成curl放到剪切板,直接在命令行粘贴就可以了。

对了,写爬虫chrome是最大的神器,推荐阅读酷壳的《CHROME开发者工具的小技巧》。

但是如此长的一个命令在命令行上修改起来特别麻烦(要让光标跳来跳去的),所以再推荐一个GUI的模拟发送请求的工具postman。

postman

postman可以让你像在chrome中修改css那样,修改请求的header,cookies等任何东西。除了全平台的app之外,还有chrome app可以用。可以将请求保存下来,下次继续工作的时候可以使用。当然postman也支持从curl粘贴导入,这样我们可以把任意chrome的请求导入到postman。不过地方比较坑,我第一次花了很长时间才找到,在 import > Paste Raw Text (竟然不是Paste cURL)。这里提醒一下,我有一次用postman的请求怎么也不对,但是用curl直接在终端就对了,这说明postman有一些bug,不能完全模拟请求。同事推荐了paw,还没用过,下次用过再谈(好像是mac专用的)。

curl to requests

这是一个在线的工具,可以将curl命令转换成python的requests库代码写成的命令(还支持node和PHP)。这样就省去了很多手工排格式的麻烦操作(其实可以做成vim插件的讲)。

python -m “json.tool”

这是python自带的json库,不用任何安装,直接用echo str | python -m 'json.tool'就可以在终端格式化json(之前我一直傻傻的粘贴到vscode里面……)

可以配合curl使用,直接让返回的json可读。

如果你使用Vim,用Vim打开一个file.json文件,可以直接在buffer中输入:%!python -m 'json.too'来将当前buffer的json内容格式化。非常有用。

EditThisCookie

顾名思义,这是用来修改cookies的。如果你要模拟登陆,可以尝试一个一个删掉cookie看哪一个是起作用的。

Proxifier

写爬虫被封ip是很正常的事情。被封了就得挂代理去访问目标网站。浏览器可以用switch Omega,终端可以用proxychains。但还是有些不方便。p4又有一些bug,并不是所有终端程序都可以走代理的。

这个时候就可以使用proxifier自定义流量规则,甚至可以全局转发到代理,一个都跑不了,非常稳!

暂时就想起来这个多,以后有别的还会在这篇博客更新。

 

介绍Python2和Python3的兼容库six

Python2和Python3有很大的不同,six这个库为此提供了一个兼容的方案。使用six写的代码可以不用修改就运行在Python2或Python3上。注意这个six并不是让Python2写的代码兼容Python3,如果你需要迁移工具,那么你要找的是2to3。如果你的代码需要同时需要运行在Python2和Python3上,那么你就需要six!

原理其实很简单,six对Python2和Python3的名字等做了统一,比如Python2的字符串叫str,Python3叫unicode,那么就可以使用six.text_type。如果运行在Python2上,six.text_type就是unicode,如果运行在Python3上,six.text_type就是str。

six库只有一个文件:six.py。这样做可以方便地拷贝到你的项目中。也可以使用pip安装。

(为什么叫six? 因为2 * 3 = 6。为什么不是+,因为*更牛逼)

下面讲一下six有哪些功能。

统一了Python2和Python3的类型

如上文提到的,使用six中的类型,能同时在Python2和Python3中正确运行,就不用自己在写代码的时候自己判断到底是Python2的unicode还是Python3的str了!

Python2:

Python3

(由于iPython不再支持Python2,所以博主用的REPL for python2是默认的,但是在这里不影响结果)

内部对象属性的重命名

Python3修改了一些解释器内部属性的名字,例如Python2的dictionary.iterlists()在Python3中变成了dictionary.lists(),(讲真我觉得Python3的名字改得好!)。

使用six可以同时兼容这两种名字,但是需要将调用的格式改为 six.iterlists(dictionary, **kwargs)。

全部的名字兼容可以参考文档。(话说我不太明白为什么保留的是Python2的名字,不用Python3的)

移动模块的位置

Python3重新组织了很多模块的位置,例如Python2的HTMLParser,在Python3中是html.parser。

我们可以使用six导入:

其他

其他的内容可以在官方的文档找到,基本上就是通过six来调用,而不是自己对Python判断。包括:

  • 提供了二进制和文本数据的兼容
  • uniittest assert的兼容
  • urllib库改动的兼容
  • 高级的自定义move

参考

https://pythonhosted.org/six

 

我的WordPress全站HTTPS啦

终于,本站的强制HTTPS已经开启了,所有的HTTP流量都会被强制转向HTTPS。

虽在早在去年我就申请了HTTPS证书,也更换了独立ip支持了HTTPS,但是并没有强制开启HTTPS,因为我发现根据网上的教程设置.htasccess会失败。后来发先用了really simple SSL插件可以做到http://kawabangga.com跳转到HTTPS,还能帮你将站内绝对路径的链接自动换成HTTPS,看起来不错,就没再管。但是一直有一个遗留问题就是,http://www.kawabangga.com是跳不过去的。

今天小红老师帮我调了一下,最后发现了问题所在。不得不说,姜还是老的辣,要是我自己调我觉得永远搞不定。

问题描述:本在在西部数码的虚拟空间上,有独立ip,支持HTTPS访问,但是HTTP访问www不会跳转到HTTPS。

首先,我们按照网上普遍的教程,在.htaccess(这是apache的配置文件)写:

会发生无限重定向,浏览器无法打开网页。所以看起来是80端口判断永远为真。然后尝试了各种别的判断条件,比如:

也还是不行。

这时候,小红老师说,不是条件判断不正确,而是到你机器上的流量永远是HTTP流量。虽然使用了HTTPS协议,但是空间提供商在内网没有必要使用HTTPS(HTTPS有加密过程更加消耗资源),所以内部是使用HTTP传输数据的。HTTPS包到达空间商的时候,空间提供商使用HTTP协议将此包转发到你的虚拟空间上(就是HTTPS外面套了一层HTTP),所以到达apache的流量永远都是HTTP流量,就发生了无限重定向。

这时候我才发现,原来西部数码自己提供了一个设置方法(放在一个不知名的小页面上,坑),如下:

果然,可以发现这里使用的参数是HTTP:From-Https,是自定义的一个参数!

另外还有一个小插曲,多次修改.htaccess的时候,小红老师看见我把所有的参数都删掉还震惊了一下,我自信的说这些我都不知道是什么,没用。于是部署好HTTPS的跳转之后,除了首页所有的页面都404了……

原来我设置了伪静态,.htaccess里面需要做相应的设置的…… 最后的正确配置如下:

终于一切都正常了。这是我一年来的一块心病啊,终于解决了。

博客四年了,停靠在西部数码也有三年了,同事们都对虚拟空间,wordpress这种东西嗤之以鼻,不过我觉得挺好的。这个博客我上来除了写东西之外几乎没有花很多精力维护,有什么问题客服解决的也很及时。虽然有很多坑,但是我觉得VPS的坑更多。况且四年前我的技术维护vps肯定有很多安全问题。这几年来wordpress的写作体验一直在提升,我很满意。对博客的SEO,amp等新技术也支持的比较好。国外的vps速度慢,国内的需要备案,对我来说选择就很有限了。所以综上看来,这几乎是我最好的选择。还是多总结,能给自己带来收获,一年多一百少一百的倒无所谓。

 

理解Python的import

Python的import是一个使用非常频繁的操作,这是在一个模块中使用另一个模块的代码的操作(几乎所有的语言都有类似的语句)。import语句是最常用的方法,但不是唯一的方法,还有importlib.import_module() 和 __import__() 等。本文解释import语句。

import做了两件事情:

  1. 搜索该名字的module(其实使用的还是内置的__import__()方法)然后初始化一个对象
  2. 将结果与本地的一个变量名绑定(默认就是这个module的名字)。

意味着,这两句是等价的:import package as pk 和 pg = __import__('package')

值得注意的是,只调用__import__()只会执行搜索动作,初始化module对象,最后丢掉该对象(因为没有赋值操作)。这点很容易将内置的__imoprt__()方法和import语句搞混。

一、Packages

Python中只有一种module类型(type),所有的module,无论是C语言实现的,还是Python实现的,还是别的,都是这种了类型。为了使名字空间有等级,Python又有packages的概念。注意这里只是一个“概念”而并不是类型,packages只是一种特殊的“module”。官方文档中将packages比作文件系统的文件夹,module比作文件(但又不完全是)。所有的package都是module,而并不是所有的module都是package。

Python的package又有两种:

Regular package

Regular package就是一个包含 __init__.py文件的文件夹。当此包导入的时候,会执行这个__init__.py文件,然后就可以包名访问包内的名字了。例如下面的文件结构:

当导入parent.one时会执行parent/__init__.py和parent/one/__init__.py

Namespace package

这是PEP 420提出的。

Namespace package由各种不同的部分(portions)组成。portion可以是zip文件,可以使网络上的资源,可以是文件系统中某个位置的资源。Namespace package在文件系统中可能没有实际的文件来表示,可能是虚拟的module。

# TODO read 420 理解namespace

下面来讲import的搜索顺序。

二、搜索

开始搜索之前,需要一个“合适的名字”(来自import语法或者__import__()函数等),即一个通过点分隔的路径名。比如foo.bar.baz,Python会先导入foo,然后导入foo.bar,然后导入foo.bar.baz,如果中间有任何失败,就会抛出ModuleNotFoundError。

缓存(sys.modules)

import首先搜索的地方是sys.modules,此为一个键值对的mapping,作为一个缓存,保存了中间形式的路径。所以如果之前import了 foo.bar.baz,那么sys.modules中就会存在foo, foo.bar, foo.bar.baz。每一个名字都对应一个module对象。

import的时候,如果在sys.modules中找到就会返回这个module对象,import过程结束。如果根据key找到了,但是value是None,就会抛出ModuleNotFoundError,如果key不存在,就继续搜索过程。

sys.modules是可写的,删除任何key并不会销毁对象(因为可能别的模块引用了这个模块),但是你可以将value设置为None强制抛出ModuleNotFoundError。下面的代码展示了这个操作(注意使用ipython结果不同,ipython在启动的时候import 了re库)。

要注意的是,如果你有module对象的引用(在sys.module的缓存),但是重新import了module对象,这两个module不会一致。也就是说你再引用一遍,代码中的该module还是从sys.module中的,所以你拿不到新的module。但是使用importlib.reload() 就可以将改module对象重新初始化一遍。这在REPL中测试代码的时候比较实用。

Finder和Loader

如果在缓存(sys.module)中没有找到的话,python就开始import机制来导入这个包。Python的import协议包括两部分:Finder和Loader。只要实现了这两部分,就算是一个importer,importer如果发现自己可以导入目标包的话,就会返回他们自己。顾名思义,Finder的任务是定位包,并不做载入的部分,Loader做真正载入包的工作。Python有三个默认的importer:一个可以导入build-in module,第二个可以导入frozen module, 第三个可以搜索import path。

三、加载

加载过程的伪代码如下:

注意一下细节:

  • sys.module已经存在目标模块的名字,import直接返回此模块
  • 在Loader执行模块代码之前,先将其放入到sys.module。这一点非常重要,可以防止循环导入(模块可能在代码中导入该模块自己)。
  • 如果loading失败,失败的模块(只有失败的模块)会从sys.module删除
  • 在module创建但是执行代码之前,import会设置 import-related 模块属性,作为后续的总结
  • 模块执行的部分才是模块的名字空间真正暴露的时候,这部分完全由loader处理。
  • loading的时候创建的模块、传入exec_module()的模块可能并不是最后返回的模块。

Loader

Loader提供了loading最重要的部分:模块的执行。import机制会调用importlib.abc.Loader.exec_module(),参数只有一个,要执行的module对象。其返回值被丢弃。Loader必须满足两个要求:

  • module必须是一个Python的module对象,Loader必须在module的全局名字空间下(module.__dict__)执行module的代码
  • 如果不能执行代码,必须抛出ImportError,其余所有异常都会被忽略

子模块

当子模块的loading执行的时候,将和父模块的名字空间绑定。参考下例:

导入foo将使spam有spam.foo属性:

就先写到这里,其实还有一个比较重要的话题就是模块的搜索顺序,以及子模块之间如何组织。新手尝尝遇到在子模块中遇到不能import其他模块的问题,以后再写吧。

彩蛋

import this可以看到Zen of Python。

import antigravity可以看到一幅漫画。

参考和了解更多

  1. python文档:https://docs.python.org/3/reference/import.html
  2. import hook和meta path
 

推荐《Hymn to life》

推荐一首音乐,一共11分钟多的音乐,前半段是Timo的音乐,后半段是卓别林在《大独裁者》中的演讲。

《大独裁者》是卓别林自导自演的一部电影,演讲呼吁人类消灭独裁和统治,追求幸福和自由,消灭国家之间的界限,鼓励士兵不要听命于愚蠢的统治,不要收体制的禁锢,鼓励进步,但是不要被机器占据了内心。

音乐链接:http://music.163.com/#/m/song?id=19275252

演讲搬运:

《Speech by Charlie (Charles) Chaplin (1889-1977) from The Great Dictator movie (1940)》
查理·卓别林(1889-1977)在《大独裁者》(1940)中的演说

I’m sorry but I don’t want to be an Emperor – that’s not my business
很抱歉,我不想做统治者——我对此毫无兴趣

I don’t want to rule or conquer anyone.
我不想统治任何人,也不想征服任何人

I should like to help everyone if possible, Jew, gentile, black man, white.
如果可以的话,我愿意帮助每一个人,不管是犹太人,非犹太人,黑人,还是白人

We all want to help one another, human beings are likethat.
我们都乐意相互帮助。这是人类的天性

We want to live by each other’s happiness, not by each other’s misery.
我们都乐意生活在他人的幸福之中,而不是眼看着他人痛苦

We don’t want to hate and despise one another.
我们都不愿互相仇恨,互相歧视

In this world there is room for everyone and the good earth is rich and can provide for everyone.
在这个世界上,有足够的空间让每个人得以生存,而土地是丰饶的,可以使每一个人都丰衣足食

The way of life can be free and beautiful.
生命可以是自由的,美好的

But we have lost the way.
但我们失去了这样的生活

Greed has poisoned men’s souls – has barricaded the world with hate;
贪婪毒害了人类的灵魂——它把世界锁在仇恨当中

has goose-stepped us into misery and bloodshed.
它操纵的军队迈着正步,将我们领入痛苦与杀戮

We have developed speed but we have shut ourselves in:
我们发展了速度,却孤立了自己

machinery that gives abundance has left us in want.
机器为我们提供了更多可能,却给我们带来了更多欲望

Our knowledge has made us cynical, our cleverness hard and unkind.
学识把我们变得刻薄,才智把我们变得冷酷无情

We think too much and feel too little: More than machinery we need humanity;
我们思考得太多,感受得太少;然而我们需要的是人性,而不是机械性

More than cleverness we need kindness and gentleness.
是温和友善,而不是才智

Without these qualities, life will be violent and all will be lost.
少了这些品质,生命将陷于暴戾,而我们将丧失一切

The aeroplane and the radio have brought us closer together.
飞行机和收音机将我们联系得更加紧密

The very nature of these inventions cries out for the goodnessin men,
这类发明的性质本身就在呼唤人类的善良

cries out for universal brotherhood for the unity of us all.
呼唤全世界的友爱团结

Even now my voice is reaching millions throughout the world,
就像现在,我的声音传向世界各地无数人们的耳中

millions of despairing men, women and little children,
传向无数的绝望的人们:男人,女人,孩子

victims of a system that makes men torture and imprison innocent people.
所有在这个折磨和禁锢无辜者的体制下承受牺牲的人们

To those who can hear me I say Do not despair.
所有能听到我声音的人们,请听我说,不要绝望

The misery that is now upon us is but the passing of greed,
我们现在所承受的不幸,仅仅是来自一部分人的贪婪和痛苦

the bitterness of men who fear the way of human progress:
他们害怕的是当今人类的进步

the hate of men will pass and dictators die, and the power they took from the people,
人们的仇恨终会消逝,独裁者也注定会死去,而他们从人民手中掳走的权利终

will return to the people and so long as men die, liberty will never perish…
将交还给人民,只要我们不畏牺牲,自由就永远也不会消失

Soldiers – don’t give yourselves to brutes, men who despise you,
战士们!别听命于残暴的畜生——他们蔑视你们

enslave you – who regiment your lives, tell you what to do, what to think and what to feel,
奴役你们,控制你们,告诉你们怎样行事,怎样思考,甚至怎样感受

who drill you, diet you, treat you like cattle, use you as cannon fodder!
他们训练你们,供养你们,像牲口似的对待你们,把你们视如炮灰

Don’t give yourselves to these unnatural men, machine men, with machine minds and machine hearts!
别听命于这些变态的人——这些有着机器头脑、机器心脏的机器人

You are not machines! You are not cattle! You are men!
你们不是机器,不是牲口,你们是人

You have the love of humanity in your hearts.
你们心里拥有的是人性之爱

You don’t hate – only the unloved hate – the unloved and the unnatural.
你们心里没有仇恨,只有得不到爱、丧失理性的人才会心怀仇恨

Soldiers – don’t fight for slavery, fight for liberty!
战士们!不要为了奴役而战!要为自由而战!

In the seventeenth chapter of Saint Luke it is written the kingdom of God is within man –
《路加福音》第十七章里写着,上帝的国度属于人类

not one man,
它不属于某一个人

nor a group of men – but in all men – in you!
不属于某一个集团,它属于全人类!属于你!

You the people have the power, the power to create machines, the power to create happiness!
人民啊,你们拥有权力,你们能创造机器,创造幸福!

You the people have the power to make this life free and beautiful,
人民啊,你们能活得自由活得美好

to make this life a wonderful adventure!
能享受生命这一场奇妙的旅程!

Then in the name of democracy let us use that power – let us all unite!!!
因此,让我们以民主的名义行使这种权力,让我们全体团结起来

Let us fight for a new world, a decent world that will give men a chance to work,
为一个崭新的世界而战,那是一个美好的世界,

that will give you the future and old age and security.
它使壮有所用,少有所求,老有所安

By the promise of these things, brutes have risen to power,
正是因为许下了这样的承诺,一群野蛮人窃取了人民的权力

but they lie!
但他们完全是在撒谎!

They do not fulfil their promise, they never will!
他们根本没能履行那些承诺!并且永远不会!

Dictators free themselves but they enslave the people!
独裁者只是让自己享受自由,却奴役他的人民

Now let us fight to fulfil that promise!
现在就让我们自己起来战斗,来兑现承诺

Let us fight to free the world, to do away with national barriers, to do away with greed,
让我们起来战斗,解放全球!

with hate and intolerance!
去消灭国家的界限!去消灭国家的界限!消灭贪欲,消灭仇恨,消灭蔑视!

Let us fight for a world of reason,
让我们为一个理性的世界而战

a world where science and progress will lead to all men’s happiness.
让科学与进步为全人类造福

Soldiers – in the name of democracy, let us all unite!!!
战士们,让我们以民主的名义团结起来!