snjl

我大概率会编程。


  • 首页

  • 标签

  • 分类

  • 归档

  • 搜索

文档测试

发表于 2019-01-31 | 分类于 python
字数统计: 798 字 | 阅读时长 ≈ 3 分钟

如果你经常阅读Python的官方文档,可以看到很多文档都有示例代码。比如re模块就带了很多示例代码:

1
2
3
4
5

>>> import re
>>> m = re.search('(?<=abc)def', 'abcdef')
>>> m.group(0)
'def'

可以把这些示例代码在Python的交互式环境下输入并执行,结果与文档中的示例代码显示的一致。

这些代码与其他说明可以写在注释中,然后,由一些工具来自动生成文档。既然这些代码本身就可以粘贴出来直接运行,那么,可不可以自动执行写在注释中的这些代码呢?

答案是肯定的。

当我们编写注释时,如果写上这样的注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

def abs(n):
'''
Function to get absolute value of number.

Example:

>>> abs(1)
1
>>> abs(-1)
1
>>> abs(0)
0
'''
return n if n >= 0 else (-n)

无疑更明确地告诉函数的调用者该函数的期望输入和输出。

并且,Python内置的“文档测试”(doctest)模块可以直接提取注释中的代码并执行测试。

doctest严格按照Python交互式命令行的输入和输出来判断测试结果是否正确。只有测试异常的时候,可以用…表示中间一大段烦人的输出。

让我们用doctest来测试上次编写的Dict类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# mydict2.py
class Dict(dict):
'''
Simple dict but also support access as x.y style.

>>> d1 = Dict()
>>> d1['x'] = 100
>>> d1.x
100
>>> d1.y = 200
>>> d1['y']
200
>>> d2 = Dict(a=1, b=2, c='3')
>>> d2.c
'3'
>>> d2['empty']
Traceback (most recent call last):
...
KeyError: 'empty'
>>> d2.empty
Traceback (most recent call last):
...
AttributeError: 'Dict' object has no attribute 'empty'
'''
def __init__(self, **kw):
super(Dict, self).__init__(**kw)

def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(r"'Dict' object has no attribute '%s'" % key)

def __setattr__(self, key, value):
self[key] = value

if __name__=='__main__':
import doctest
doctest.testmod()

运行python mydict2.py:

1
2

$ python mydict2.py

什么输出也没有。这说明我们编写的doctest运行都是正确的。如果程序有问题,比如把getattr()方法注释掉,再运行就会报错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

$ python mydict2.py
**********************************************************************
File "/Users/michael/Github/learn-python3/samples/debug/mydict2.py", line 10, in __main__.Dict
Failed example:
d1.x
Exception raised:
Traceback (most recent call last):
...
AttributeError: 'Dict' object has no attribute 'x'
**********************************************************************
File "/Users/michael/Github/learn-python3/samples/debug/mydict2.py", line 16, in __main__.Dict
Failed example:
d2.c
Exception raised:
Traceback (most recent call last):
...
AttributeError: 'Dict' object has no attribute 'c'
**********************************************************************
1 items had failures:
2 of 9 in __main__.Dict
***Test Failed*** 2 failures.

注意到最后3行代码。当模块正常导入时,doctest不会被执行。只有在命令行直接运行时,才执行doctest。所以,不必担心doctest会在非测试环境下执行。

小结

doctest非常有用,不但可以用来测试,还可以直接作为示例代码。通过某些文档生成工具,就可以自动把包含doctest的注释提取出来。用户看文档的时候,同时也看到了doctest。

Ubuntu:关机、重启、注销等命令

发表于 2019-01-31 | 分类于 服务器
字数统计: 706 字 | 阅读时长 ≈ 2 分钟

shutdown

shutdown命令用来系统关机命令。shutdown指令可以关闭所有程序,并依用户的需要,进行重新开机或关机的动作。

语法

1
shutdown(选项)(参数)

选项

1
2
3
4
5
6
7
8
-c:当执行“shutdown -h 11:50”指令时,只要按+键就可以中断关机的指令;
-f:重新启动时不执行fsck;
-F:重新启动时执行fsck;
-h:将系统关机;
-k:只是送出信息给所有用户,但不会实际关机;
-n:不调用init程序进行关机,而由shutdown自己进行;
-r:shutdown之后重新启动;
-t<秒数>:送出警告信息和删除信息之间要延迟多少秒。

参数

  • [时间]:设置多久时间后执行shutdown指令;
  • [警告信息]:要传送给所有登入用户的信息。

    实例

    指定现在立即关机:
    1
    shutdown -h now

指定5分钟后关机,同时送出警告信息给登入用户:

1
shutdown +5 "System will shutdown after 5 minutes"

其他实例

1
2
3
4
5
6
7
8
9
10
11
shutdown -h now 现在立即关机

shutdown -r now 现在立即重启

shutdown -r +3 三分钟后重启

shutdown -h +3 “The System will shutdown after 3 minutes” 提示使用者将在三分钟后关机

shutdown -r 20:23 在20:23时将重启计算机

shutdown -r 20:23 & 可以将在20:23时重启的任务放到后台去,用户可以继续操作终端

reboot

reboot命令用来重新启动正在运行的Linux操作系统。

语法

1
reboot(选项)

选项

1
2
3
4
5
-d:重新开机时不把数据写入记录文件/var/tmp/wtmp。本参数具有“-n”参数效果;
-f:强制重新开机,不调用shutdown指令的功能;
-i:在重开机之前,先关闭所有网络界面;
-n:重开机之前不检查是否有未结束的程序;
-w:仅做测试,并不真正将系统重新开机,只会把重开机的数据写入/var/log目录下的wtmp记录文件。

实例

1
2
reboot        //重开机。
reboot -w //做个重开机的模拟(只有纪录并不会真的重开机)。

logout

logout命令用于退出当前登录的Shell,logout指令让用户退出系统,其功能和login指令相互对应。

login

login命令用于给出登录界面,可用于重新登录或者切换用户身份,也可通过它的功能随时更换登入身份。在Slackware发行版中 ,您可在命令后面附加欲登入的用户名称,它会直接询问密码,等待用户输入。当/etc/nologin文件存在时,系统只root帐号登入系统,其他用户一律不准登入。

语法

1
login(选项)(参数)

选项

  • -p:告诉login指令不销毁环境变量;
  • -h:指定远程服务器的主机名。

    参数

    1
    用户名:指定登录使用的用户名。

无界面selenium

发表于 2019-01-31 | 分类于 爬虫
字数统计: 502 字 | 阅读时长 ≈ 2 分钟

selenium中文文档:https://selenium-python-zh.readthedocs.io/en/latest/index.html

使用selenium,安装好对应版本的chromedriver和chrome,然后将driver放入项目,用 driver = webdriver.Chrome()启动。

selenium刷新:

1
2
3
driver.refresh()
# 或调用js
driver.execute_script("location.reload()")

请求头配置参考:https://blog.csdn.net/u013440574/article/details/81911954

仅添加普通请求头:

1
2
3
4
5
6
opt = webdriver.ChromeOptions()
opt.set_headless()
opt.add_argument(
'user-agent=Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6726.400 QQBrowser/10.2.2265.400')

driver = webdriver.Chrome(options=opt)

这里使用opt作为chromedriver的参数,添加user-agent信息,然后调用webdriver.Chrome,并传入opt。

注:这里使用的是无窗口界面的chrome selenium爬虫,所以可以设置,如果使用有界面的,则不必设置,因为带界面必定带请求头和各种信息。

Selenium不再推荐使用PhantomJS,会报如下警告

1
2
UserWarning: Selenium support for PhantomJS has been deprecated, please use headless versions of Chrome or Firefox instead
warnings.warn('Selenium support for PhantomJS has been deprecated, please use headless '

于是从PhantomJS转移到Chrome,使用headless versions of Chrome时,首先要安装Chrome,然后下载chromedriver,再把chromedriver的地址配置到系统环境变量path中,方便调用。如果不把chromedriver的地址配置到系统环境变量的话,也可以在使用时指定chromedriver的地址。

注意Chrome和chromedriver有版本对应的要求,系统中安装了某一版本的chrome要使用对应版本的chromedriver,其实下载最新版本的Chrome和chromedriver就行了,一般都是对应的。

Chrome下载地址:https://chrome.en.softonic.com/

chromedriver下载地址:http://npm.taobao.org/mirrors/chromedriver/

报错:[0917/002914.533:ERROR:gpu_process_transport_factory.cc(1007)] Lost UI shared context.

原因是在windows系统中Chrome无头模式下,其中的SwiftShader软件会触发断言失败,但实际上不影响程序执行,可以忽略该错误。

可以设置chromedriver的日志级别,只有大于设置级别的日志还会输出,该配置参数为:log-level:

1
2
3
4
5
6
opt.add_argument('log-level=3')
# INFO = 0,
# WARNING = 1,
# LOG_ERROR = 2,
# LOG_FATAL = 3
# default is 0

python IO编程

发表于 2019-01-31 | 分类于 python
字数统计: 934 字 | 阅读时长 ≈ 3 分钟

IO在计算机中指Input/Output,也就是输入和输出。由于程序和运行时数据是在内存中驻留,由CPU这个超快的计算核心来执行,涉及到数据交换的地方,通常是磁盘、网络等,就需要IO接口。

比如你打开浏览器,访问新浪首页,浏览器这个程序就需要通过网络IO获取新浪的网页。浏览器首先会发送数据给新浪服务器,告诉它我想要首页的HTML,这个动作是往外发数据,叫Output,随后新浪服务器把网页发过来,这个动作是从外面接收数据,叫Input。所以,通常,程序完成IO操作会有Input和Output两个数据流。当然也有只用一个的情况,比如,从磁盘读取文件到内存,就只有Input操作,反过来,把数据写到磁盘文件里,就只是一个Output操作。

IO编程中,Stream(流)是一个很重要的概念,可以把流想象成一个水管,数据就是水管里的水,但是只能单向流动。Input Stream就是数据从外面(磁盘、网络)流进内存,Output Stream就是数据从内存流到外面去。对于浏览网页来说,浏览器和新浪服务器之间至少需要建立两根水管,才可以既能发数据,又能收数据。

由于CPU和内存的速度远远高于外设的速度,所以,在IO编程中,就存在速度严重不匹配的问题。举个例子来说,比如要把100M的数据写入磁盘,CPU输出100M的数据只需要0.01秒,可是磁盘要接收这100M数据可能需要10秒,怎么办呢?有两种办法:

第一种是CPU等着,也就是程序暂停执行后续代码,等100M的数据在10秒后写入磁盘,再接着往下执行,这种模式称为同步IO;

另一种方法是CPU不等待,只是告诉磁盘,“您老慢慢写,不着急,我接着干别的事去了”,于是,后续代码可以立刻接着执行,这种模式称为异步IO。

同步和异步的区别就在于是否等待IO执行的结果。好比你去麦当劳点餐,你说“来个汉堡”,服务员告诉你,对不起,汉堡要现做,需要等5分钟,于是你站在收银台前面等了5分钟,拿到汉堡再去逛商场,这是同步IO。

你说“来个汉堡”,服务员告诉你,汉堡需要等5分钟,你可以先去逛商场,等做好了,我们再通知你,这样你可以立刻去干别的事情(逛商场),这是异步IO。

很明显,使用异步IO来编写程序性能会远远高于同步IO,但是异步IO的缺点是编程模型复杂。想想看,你得知道什么时候通知你“汉堡做好了”,而通知你的方法也各不相同。如果是服务员跑过来找到你,这是回调模式,如果服务员发短信通知你,你就得不停地检查手机,这是轮询模式。总之,异步IO的复杂度远远高于同步IO。

操作IO的能力都是由操作系统提供的,每一种编程语言都会把操作系统提供的低级C接口封装起来方便使用,Python也不例外。我们后面会详细讨论Python的IO编程接口。

注意,本章的IO编程都是同步模式,异步IO由于复杂度太高,后续涉及到服务器端程序开发时我们再讨论。

requests库的get和post方法

发表于 2019-01-31 | 分类于 python
字数统计: 1.5k 字 | 阅读时长 ≈ 8 分钟

获取网页的方式

其实在加载网页的时候, 有几种类型, 而这几种类型就是你打开网页的关键. 最重要的类型 (method) 就是 get 和 post (当然还有其他的, 比如 head, delete)。

以下分析两个重要的类型的重要特点。

post

  • 账号登录
  • 搜索内容
  • 上传图片
  • 上传文件
  • 往服务器传数据等

    get

  • 正常打开网页
  • 不往服务器传数据
    这样看来, 很多网页使用 get 就可以了, 而 post, 我们则是给服务器发送个性化请求, 比如将你的账号密码传给服务器, 让它给你返回一个含有你个人信息的 HTML.

从主动和被动的角度来说, post 中文是发送, 比较主动, 你控制了服务器返回的内容. 而 get 中文是取得, 是被动的, 你没有发送给服务器个性化的信息, 它不会根据你个性化的信息返回不一样的 HTML.

get方法

get请求的参数一般是在网址后面加入?parameter1=xxx&parameter2=xxxx,使用?传递参数,用&并列参数。

使用requests的包直接请求baidu,如下所示:

1
2
3
4
>>> r = requests.get("http://www.baidu.com")
>>> r.text
'<!DOCTYPE html>\r\n<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=http://s1.bdstatic.com/r/www/cache/bdorz/baidu.min.css><title>ç\x99¾åº¦ä¸\x80ä
···

r.text得到的是unicode编码数据,可能会出现乱码,可以使用r.encoding = ‘utf8’强制转换后再提取

1
2
3
4
>>> r.encoding='utf8'
>>> r.text
'<!DOCTYPE html>\r\n<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=http://s1.bdstatic.com/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head>
···

注意:注意:response.content得到的是二进制数据,而response.text得到的是Unicode编码数据,一般content用于获取图片、视频等,text用于获取文字类数据。

访问http://httpbin/get

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> r = requests.get('http://httpbin.org/get')
>>> print(r.text)
{
"args": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Connection": "close",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.18.4"
},
"origin": "xxx.xxx.xxx.xxx",
"url": "http://httpbin.org/get"
}

如果带参数:

1
2
3
4
5
6
7
8
9
>>> r = requests.get('http://httpbin.org/get?a=2&c=3&w=')
>>> print(r.text)
{
"args": {
"a": "2",
"c": "3",
"w": ""
},
···

可以看到他获取到了参数并且输出了。

也可以使用另一种写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> r = requests.get('http://httpbin.org/get',params=parameter)
>>> print(r.text)
{
"args": {
"a": "23",
"b": "32",
"c": "string"
},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Connection": "close",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.18.4"
},
"origin": "xxx.xxx.xxx.xxx",
"url": "http://httpbin.org/get?a=23&b=32&c=string"
}

可以直接使用json方法转化成json(与使用json的loads效果一样):

1
2
3
4
>>> r.json
<bound method Response.json of <Response [200]>>
>>> r.json()
{'args': {'a': '23', 'b': '32', 'c': 'string'}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Connection': 'close', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.18.4'}, 'origin': '115.153.174.11', 'url': 'http://httpbin.org/get?a=23&b=32&c=string'}

有一些网页不使用headers无法访问,例如知乎,使用headers:

1
2
3
4
5
6
7
8
>>> headers = {'User-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.5221.400 QQBrowser/10.0.1125.400'}
>>> r = requests.get("http://www.zhihu.com")
>>> print(r)
<Response [400]>
>>> r = requests.get("http://www.zhihu.com",headers=headers)
>>> r
<Response [200]>
>>>

可以通过r.headers看到请求头信息

1
2
>>> r.headers
{'Date': 'Tue, 29 Jan 2019 09:05:50 GMT', 'Content-Type': 'text/html; charset=utf-8', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Vary': 'Accept-Encoding', 'Content-Security-Policy': "default-src * blob:; img-src * data: blob:; connect-src * wss: blob:; frame-src 'self' *.zhihu.com weixin: *.vzuu.com getpocket.com note.youdao.com safari-extension://com.evernote.safari.clipper-Q79WDW8YH9 zhihujs: captcha.guard.qcloud.com; script-src 'self' blob: *.zhihu.com res.wx.qq.com 'unsafe-eval' unpkg.zhimg.com unicom.zhimg.com captcha.gtimg.com captcha.guard.qcloud.com pagead2.googlesyndication.com i.hao61.net 'nonce-a0691bfd-cf49-40b4-8ad7-37d552f59c50'; style-src 'self' 'unsafe-inline' *.zhihu.com unicom.zhimg.com captcha.gtimg.com", 'X-Frame-Options': 'SAMEORIGIN', 'Strict-Transport-Security': 'max-age=15552000; includeSubDomains', 'Surrogate-Control': 'no-store', 'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate', 'Pragma': 'no-cache', 'Expires': '0', 'X-Content-Type-Options': 'nosniff', 'X-XSS-Protection': '1; mode=block', 'Content-Encoding': 'gzip', 'Server': 'ZWS'}

post方法

最简单的依然是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> r = requests.post("http://httpbin.org/post")
>>> print(r.text)
{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Connection": "close",
"Content-Length": "0",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.18.4"
},
"json": null,
"origin": "xxx.xxx.xxx.xxx",
"url": "http://httpbin.org/post"
}

传入参数:

1
2
3
4
5
6
7
8
9
>>> r = requests.post("http://httpbin.org/post",params=parameter)
>>> print(r.text)
{
"args": {
"a": "23",
"b": "32",
"c": "string"
},
···

使用headers和get方法一样。

错误处理

发表于 2019-01-31 | 分类于 python
字数统计: 2.4k 字 | 阅读时长 ≈ 10 分钟

在程序运行的过程中,如果发生了错误,可以事先约定返回一个错误代码,这样,就可以知道是否有错,以及出错的原因。在操作系统提供的调用中,返回错误码非常常见。比如打开文件的函数open(),成功时返回文件描述符(就是一个整数),出错时返回-1。

用错误码来表示是否出错十分不便,因为函数本身应该返回的正常结果和错误码混在一起,造成调用者必须用大量的代码来判断是否出错:

1
2
3
4
5
6
7
8
9
10
11
12
13
def foo():
r = some_function()
if r==(-1):
return (-1)
# do something
return r

def bar():
r = foo()
if r==(-1):
print('Error')
else:
pass

一旦出错,还要一级一级上报,直到某个函数可以处理该错误(比如,给用户输出一个错误信息)。

所以高级语言通常都内置了一套try…except…finally…的错误处理机制,Python也不例外。

try

让我们用一个例子来看看try的机制:

1
2
3
4
5
6
7
8
9
try:
print('try...')
r = 10 / 0
print('result:', r)
except ZeroDivisionError as e:
print('except:', e)
finally:
print('finally...')
print('END')

当我们认为某些代码可能会出错时,就可以用try来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即except语句块,执行完except后,如果有finally语句块,则执行finally语句块,至此,执行完毕。

上面的代码在计算10 / 0时会产生一个除法运算错误:

1
2
3
4
try...
except: division by zero
finally...
END

从输出可以看到,当错误发生时,后续语句print(‘result:’, r)不会被执行,except由于捕获到ZeroDivisionError,因此被执行。最后,finally语句被执行。然后,程序继续按照流程往下走。

如果把除数0改成2,则执行结果如下:

1
2
3
4
try...
result: 5
finally...
END

由于没有错误发生,所以except语句块不会被执行,但是finally如果有,则一定会被执行(可以没有finally语句)。

你还可以猜测,错误应该有很多种类,如果发生了不同类型的错误,应该由不同的except语句块处理。没错,可以有多个except来捕获不同类型的错误:

1
2
3
4
5
6
7
8
9
10
11
try:
print('try...')
r = 10 / int('a')
print('result:', r)
except ValueError as e:
print('ValueError:', e)
except ZeroDivisionError as e:
print('ZeroDivisionError:', e)
finally:
print('finally...')
print('END')

int()函数可能会抛出ValueError,所以我们用一个except捕获ValueError,用另一个except捕获ZeroDivisionError。

此外,如果没有错误发生,可以在except语句块后面加一个else,当没有错误发生时,会自动执行else语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
try:
print('try...')
r = 10 / int('2')
print('result:', r)
except ValueError as e:
print('ValueError:', e)
except ZeroDivisionError as e:
print('ZeroDivisionError:', e)
else:
print('no error!')
finally:
print('finally...')
print('END')

Python的错误其实也是class,所有的错误类型都继承自BaseException,所以在使用except时需要注意的是,它不但捕获该类型的错误,还把其子类也“一网打尽”。比如:

1
2
3
4
5
6
try:
foo()
except ValueError as e:
print('ValueError')
except UnicodeError as e:
print('UnicodeError')

第二个except永远也捕获不到UnicodeError,因为UnicodeError是ValueError的子类,如果有,也被第一个except给捕获了。

Python所有的错误都是从BaseException类派生的,常见的错误类型和继承关系看这里:

https://docs.python.org/3/library/exceptions.html#exception-hierarchy

使用try…except捕获错误还有一个巨大的好处,就是可以跨越多层调用,比如函数main()调用foo(),foo()调用bar(),结果bar()出错了,这时,只要main()捕获到了,就可以处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
def foo(s):
return 10 / int(s)

def bar(s):
return foo(s) * 2

def main():
try:
bar('0')
except Exception as e:
print('Error:', e)
finally:
print('finally...')

也就是说,不需要在每个可能出错的地方去捕获错误,只要在合适的层次去捕获错误就可以了。这样一来,就大大减少了写try…except…finally的麻烦。

调用栈

如果错误没有被捕获,它就会一直往上抛,最后被Python解释器捕获,打印一个错误信息,然后程序退出。来看看err.py:

1
2
3
4
5
6
7
8
9
10
11
# err.py:
def foo(s):
return 10 / int(s)

def bar(s):
return foo(s) * 2

def main():
bar('0')

main()

执行,结果如下:

1
2
3
4
5
6
7
8
9
10
11
$ python3 err.py
Traceback (most recent call last):
File "err.py", line 11, in <module>
main()
File "err.py", line 9, in main
bar('0')
File "err.py", line 6, in bar
return foo(s) * 2
File "err.py", line 3, in foo
return 10 / int(s)
ZeroDivisionError: division by zero

出错并不可怕,可怕的是不知道哪里出错了。解读错误信息是定位错误的关键。我们从上往下可以看到整个错误的调用函数链:

错误信息第1行:

1
Traceback (most recent call last):

告诉我们这是错误的跟踪信息。

第2~3行:

1
2
File "err.py", line 11, in <module>
main()

调用main()出错了,在代码文件err.py的第11行代码,但原因是第9行:

1
2
File "err.py", line 9, in main
bar('0')

调用bar(‘0’)出错了,在代码文件err.py的第9行代码,但原因是第6行:

1
2
File "err.py", line 6, in bar
return foo(s) * 2

原因是return foo(s) * 2这个语句出错了,但这还不是最终原因,继续往下看:

1
2
File "err.py", line 3, in foo
return 10 / int(s)

原因是return 10 / int(s)这个语句出错了,这是错误产生的源头,因为下面打印了:

1
ZeroDivisionError: integer division or modulo by zero

根据错误类型ZeroDivisionError,我们判断,int(s)本身并没有出错,但是int(s)返回0,在计算10 / 0时出错,至此,找到错误源头。

出错的时候,一定要分析错误的调用栈信息,才能定位错误的位置。*

记录错误

如果不捕获错误,自然可以让Python解释器来打印出错误堆栈,但程序也被结束了。既然我们能捕获错误,就可以把错误堆栈打印出来,然后分析错误原因,同时,让程序继续执行下去。

Python内置的logging模块可以非常容易地记录错误信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# err_logging.py

import logging

def foo(s):
return 10 / int(s)

def bar(s):
return foo(s) * 2

def main():
try:
bar('0')
except Exception as e:
logging.exception(e)

main()
print('END')

同样是出错,但程序打印完错误信息后会继续执行,并正常退出:

1
2
3
4
5
6
7
8
9
10
11
12

$ python3 err_logging.py
ERROR:root:division by zero
Traceback (most recent call last):
File "err_logging.py", line 13, in main
bar('0')
File "err_logging.py", line 9, in bar
return foo(s) * 2
File "err_logging.py", line 6, in foo
return 10 / int(s)
ZeroDivisionError: division by zero
END

通过配置,logging还可以把错误记录到日志文件里,方便事后排查。

抛出错误

因为错误是class,捕获一个错误就是捕获到该class的一个实例。因此,错误并不是凭空产生的,而是有意创建并抛出的。Python的内置函数会抛出很多类型的错误,我们自己编写的函数也可以抛出错误。

如果要抛出错误,首先根据需要,可以定义一个错误的class,选择好继承关系,然后,用raise语句抛出一个错误的实例:

1
2
3
4
5
6
7
8
9
10
11
12

# err_raise.py
class FooError(ValueError):
pass

def foo(s):
n = int(s)
if n==0:
raise FooError('invalid value: %s' % s)
return 10 / n

foo('0')

执行,可以最后跟踪到我们自己定义的错误:

1
2
3
4
5
6
7
8

$ python3 err_raise.py
Traceback (most recent call last):
File "err_throw.py", line 11, in <module>
foo('0')
File "err_throw.py", line 8, in foo
raise FooError('invalid value: %s' % s)
__main__.FooError: invalid value: 0

只有在必要的时候才定义我们自己的错误类型。如果可以选择Python已有的内置的错误类型(比如ValueError,TypeError),尽量使用Python内置的错误类型。

最后,我们来看另一种错误处理的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# err_reraise.py

def foo(s):
n = int(s)
if n==0:
raise ValueError('invalid value: %s' % s)
return 10 / n

def bar():
try:
foo('0')
except ValueError as e:
print('ValueError!')
raise

bar()

在bar()函数中,我们明明已经捕获了错误,但是,打印一个ValueError!后,又把错误通过raise语句抛出去了,这不有病么?

其实这种错误处理方式不但没病,而且相当常见。捕获错误目的只是记录一下,便于后续追踪。但是,由于当前函数不知道应该怎么处理该错误,所以,最恰当的方式是继续往上抛,让顶层调用者去处理。好比一个员工处理不了一个问题时,就把问题抛给他的老板,如果他的老板也处理不了,就一直往上抛,最终会抛给CEO去处理。

raise语句如果不带参数,就会把当前错误原样抛出。此外,在except中raise一个Error,还可以把一种类型的错误转化成另一种类型:

1
2
3
4
5

try:
10 / 0
except ZeroDivisionError:
raise ValueError('input error!')

只要是合理的转换逻辑就可以,但是,决不应该把一个IOError转换成毫不相干的ValueError。

小结

Python内置的try…except…finally用来处理错误十分方便。出错时,会分析错误信息并定位错误发生的代码位置才是最关键的。

程序也可以主动抛出错误,让调用者来处理相应的错误。但是,应该在文档中写清楚可能会抛出哪些错误,以及错误产生的原因。

scrapy pycharm调试

发表于 2019-01-31 | 分类于 scrapy , 爬虫
字数统计: 188 字 | 阅读时长 ≈ 1 分钟

1.参考网站
https://blog.csdn.net/heyifei88/article/details/53197797

2.另一种方式(更好一些)

1
2
3
4
5
6
7
8
import sys
import os
from scrapy.cmdline import execute

# 获取当前文件所在目录
current_dir = os.path.dirname(os.path.abspath(__file__)) # 当前文件的绝对路径,然后再找他的父级目录
sys.path.append(current_dir) # 将当前路径加入到path中
execute(['scrapy', 'crawl', '爬虫名','参数'])

例如:

1
2
3
4
5
6
7
8
import sys
import os
from scrapy.cmdline import execute

# 获取当前文件所在目录
current_dir = os.path.dirname(os.path.abspath(__file__)) # 当前文件的绝对路径,然后再找他的父级目录
sys.path.append(current_dir) # 将当前路径加入到path中
execute(['scrapy', 'crawl', 'quotes', '-o quotes1.jl'])

也可以不带参数:

1
2
···
execute(['scrapy', 'crawl', 'quotes'])

scrapy架构

发表于 2019-01-31 | 分类于 scrapy , 爬虫
字数统计: 1.4k 字 | 阅读时长 ≈ 5 分钟

image

Scrapy Engine(引擎)

负责Spider、ItemPipeline、Downloader、Scheduler中间的通讯,信号、数据传递等。

Scheduler(调度器)

它负责接受引擎发送过来的Request请求,并按照一定的方式进行整理排列,入队,当引擎需要时,交还给引擎。初始的爬取URL和后续在页面中获取的待爬取的URL将放入调度器中,等待爬取。同时调度器会自动去除重复的URL(如果特定的URL不需要去重也可以通过设置实现,如post请求的URL)

Downloader(下载器)

负责下载Scrapy Engine(引擎)发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎),由引擎交给Spider来处理,

Spider(爬虫)

它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler(调度器).

Item Pipeline(管道)

它负责处理Spider中获取到的Item,并进行进行后期处理(详细分析、过滤、存储等)的地方。

Downloader Middlewares(下载中间件)

你可以当作是一个可以自定义扩展下载功能的组件。

Spider Middlewares(Spider中间件)

可以理解为是一个可以自定扩展和操作引擎和Spider中间通信的功能组件(比如进入Spider的Responses;和从Spider出去的Requests)

运作流程

  1. 引擎打开一个网站(open a domain),找到处理该网站的Spider并向该spider请求第一个要爬取的URL(s)。

  2. 引擎从Spider中获取到第一个要爬取的URL并在调度器(Scheduler)以Request调度。

  3. 引擎向调度器请求下一个要爬取的URL。

  4. 调度器返回下一个要爬取的URL给引擎,引擎将URL通过下载中间件(请求(request)方向)转发给下载器(Downloader)。

  5. 一旦页面下载完毕,下载器生成一个该页面的Response,并将其通过下载中间件(返回(response)方向)发送给引擎。

  6. 引擎从下载器中接收到Response并通过Spider中间件(输入方向)发送给Spider处理。

  7. Spider处理Response并返回爬取到的Item及(跟进的)新的Request给引擎。

  8. 引擎将(Spider返回的)爬取到的Item给Item Pipeline,将(Spider返回的)Request给调度器。

9.(从第二步)重复直到调度器中没有更多地request,引擎关闭该网站。

通俗解释

  1. 引擎:Hi!Spider, 你要处理哪一个网站?
  2. Spider:老大要我处理xxxx网页。
  3. 引擎:你把第一个需要处理的URL给我吧。
  4. Spider:给你,第一个URL是xxxxxxx
  5. 引擎:Hi!调度器,我这有request请求你帮我排序入队一下。
  6. 调度器:好的,正在处理你等一下。
  7. 引擎:Hi!调度器,把你处理好的request请求给我。
  8. 调度器:给你,这是我处理好的request
  9. 引擎:Hi!下载器,你按照老大的下载中间件的设置帮我下载一下这个request请求
  10. 下载器:好的!给你,这是下载好的东西。(如果失败:sorry,这个request下载失败了。然后引擎告诉调度器,这个request下载失败了,你记录一下,我们待会儿再下载)
  11. 引擎:Hi!Spider,这是下载好的东西,并且已经按照老大的下载中间件处理过了,你自己处理一下(注意!这儿responses默认是交给def parse()这个函数处理的)
  12. Spider:(处理完毕数据之后对于需要跟进的URL),Hi!引擎,我这里有两个结果,这个是我需要跟进的URL,还有这个是我获取到的Item数据。
  13. 引擎:Hi !管道 我这儿有个item你帮我处理一下!调度器!这是需要跟进URL你帮我处理下。然后从第四步开始循环,直到获取完老大需要全部信息。
  14. 管道调度器:好的,现在就做!

注意!只有当调度器中不存在任何request了,整个程序才会停止,(也就是说,对于下载失败的URL,Scrapy也会重新下载。)

Spider类

Spider是用户编写用于从单个网站(或者一些网站)爬取数据的类。

其包含了一个用于下载的初始URL,如何跟进网页中的链接以及如何分析页面中的内容, 提取生成 item 的方法。

为了创建一个Spider,您必须继承 scrapy.Spider 类, 且定义以下三个属性:

  • name: 用于区别Spider。 该名字必须是唯一的,不可以为不同的Spider设定相同的名字。
  • start_urls: 包含了Spider在启动时进行爬取的url列表。 因此,第一个被获取到的页面将是其中之一。 后续的URL则从初始的URL获取到的数据中提取。
  • parse() 是spider的一个方法。 被调用时,每个初始URL完成下载后生成的 Response 对象将会作为唯一的参数传递给该函数。 该方法负责解析返回的数据(response data),提取数据(生成item)以及生成需要进一步处理的URL的 Request 对象。

制作Scrapy爬虫步骤

  1. 新建项目 (scrapy startproject xxx):新建一个新的爬虫项目
  2. 明确目标 (编写items.py):明确你想要抓取的目标
    3.制作爬虫 (spiders/xxspider.py):制作爬虫开始爬取网页
  3. 存储内容 (pipelines.py):设计管道存储爬取内容

新建项目目录

1
scrapy startproject mySpider

目录

1
2
3
4
5
6
7
8
9
10
mySpider/
scrapy.cfg
mySpider/
__init__.py
items.py
pipelines.py
settings.py
spiders/
__init__.py
...

其中:

  • scrapy.cfg: 项目的配置文件。
  • mySpider/: 项目的Python模块,将会从这里引用代码。
  • mySpider/items.py: 项目的目标文件。
  • mySpider/pipelines.py: 项目的管道文件。
  • mySpider/settings.py: 项目的设置文件。
  • mySpider/spiders/: 存储爬虫代码目录。

Ubuntu18.04安装Mysql时未让输入密码

发表于 2019-01-31 | 分类于 服务器
字数统计: 222 字 | 阅读时长 ≈ 1 分钟

在ubuntu18.04中使用

1
apt install mysql-server

安装数据库时,在安装过程中,并没有像ubuntu16中提示输入root用户的密码,所以无法登录。

原因

mysql5.7暂时不支持ubuntu18.04,所以需要安装mysql8.0,需要引入mysql的deb。

解决办法

下载 https://dev.mysql.com/downloads/repo/apt/ 中的deb包。
使用

1
dpkg -i deb_name

根据需求选择配置。

然后

1
2
apt update 
apt install mysql-server

此时的安装过程就会提示输入root密码,安装完成后普通用户也能直接登录。

注:mysql8.0很多配置方面与mysql5.7不一样,所以可能需要先使用ubuntu16.04安装好mysql后更新到18.04,或者不更新,或者等mysql5.7支持18.04版本后再安装。或者使用docker。。。

mysql8.0使用原先配置产生的问题(已发现)

设置mysqld的sql_mode的时候会出现bug,无法设置。

scrapy项目测试

发表于 2019-01-31 | 分类于 scrapy , 爬虫
字数统计: 2.8k 字 | 阅读时长 ≈ 13 分钟

项目地址:https://github.com/snjl/python.spider.scrapy_test.git

新建项目

在开始爬取之前,必须创建一个新的Scrapy项目。进入自定义的项目目录中,运行下列命令:

1
scrapy startproject tutorial

阅读全文 »
1…456…21
snjl

snjl

越过山丘,才发现无人等候。

203 日志
44 分类
107 标签
RSS
GitHub E-Mail Weibo
© 2019 snjl
总访问量次 | 总访客人 |
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.4