Ubuntu 16.04安装python3.6.2

前言

自从昨天把电脑的双系统 win10(1703) + Ubuntu (16.04) 安装好后 就打算 以后在使用时尽量linux系统! 一来是为了更快上手 Linux 虽然有种说法 “学习Linux 虚拟机就够了”,emm ,不得不承认 虚拟机利用的好 确实是不错的选择,可是对于我,更想以更加亲近地方式接近 Linux ,所以呢不怕折腾的我 到底还是把系统装上了 二来 也基于对Linux 的向往 虽然新版的win10 更加迎合用户体验 , 我还是 选择 去折腾linux , Linux 也有不一样的美 所以 打算把编译环境都搬到这里

关于 Ubuntu16.04

Ubuntu16.04 上默认使用 Python2.7 和 python3.5 (可以分别使用 命令行 $ python2 -v 、$ python3 -v 查看Ubuntu上的python 版本 )

如果想要安装最新的python 版本 python3.6x 的可以通过python官网 提供的源代码安装

Ubuntun 安装 python 3.6.2

使以下命令行 :

# wget https://www.python.org/ftp/python/3.6.2/Python-3.6.2.tar.xz
# tar -xvf Python-3.6.2.tar.xz
# cd Python-3.6.2
# ./configure
# make
# make install

备注:

1 . “Python-3.6.2.tar.xz ” 这个包 也可以 通过python官网网站直接下载然后定位到 下载目录 在目录下 解压安装此包

如 我是直接接下载到相应目录下解压的

~$cd /media/trsky/系统/编码软件工具
~$ cd  Python-3.6.2
~$./configure
~$ make
~$ make install
~$ sudo make install

2. 如果 在这过程中被提示 “权限不够” :

  • ubuntu 的权限不够, 这是因为 进入ubuntu的身份是自己默认登入名的那种情况 而不是 root ,使用自己默认登录名的情况下 ” 创建文件的权限不够 ” ,使用 root 身份即可获得相应权限

  • 在终端上 , 在 需要执行的命令行之前 加上 ” sudo ” ,使用root 权限 ,然后按照提示 输入用户密码就可以执行想做的操作了

  • 或者,在linux终端命令下 ,使用 “sudo -i ” 命令 直接进入 root 身份 用 ” logout ” 退出 root 权限 ,也可以 ” sudo su root ” 进入 ” exit ” 退出 ,例如 开始时是 ” user@ubuntu:~$” , 使用命 令后 ,变为 ” root@ubuntu:~#” , 改变了用户对一些命令的权限

如 我的终端输出情况 :

trsky@trsky-ubuntu:~$ sudo -i
root@trsky-ubuntu:~# logout
trsky@trsky-ubuntu:~$ sudo su root
root@trsky-ubuntu:/home/trsky# exit
exit
trsky@trsky-ubuntu:~$ 

3. 如果 提示 ” can’t decompress data; zlib not available ”

缺少依赖包 ” zlib ” ,则安装相应的包

# 安装依赖zlib、zlib-devel
# 在ubuntu里,zlib叫zlib1g,相应的zlib-devel叫zlib1g-dev
$ sudo apt-get install zlib1g
$ sudo apt-get install zlib1g-dev

4. 安装完后的一些操作

查看python3安装路径 和 版本

~ which python3
~ python3 -V

直接在 Linux 终端上也可以直接 ” ~$ python3 ” 查看python3 版本是否为自己所要更新的版本

5. 进入 python

要出现 Python提示符号

~$ python3

退出Python提示符

~$ quit() 
# 或者 
~$ exit() 

当你看到的python3 版本为Python 3.6.2 , 那么 Python 3.6.2 成功安装在了系统上!!

CentOS 6 Python2.6.6升级至Python2.7.6教程

有时候有些程序需要在Python 2.7才能运行,而CentOS6.x系统默认的是Python 2.6,遇到这个情况我们可以选择升级python2.6.6升级到Python2.7.6,或直接安装CentOS7.x版本,因为7.x版本系统默认的是Python 2.7。

1、首先使用“python -V”命令查看python版本,蜗牛测试主机显示的是2.6.6版,于是下面就着手将python2.6.6升级到Python2.7.6。

python -V #查看python版本

未分类

2、接下来先请完成系统升级及安装开发工具包及相关需要的组件。只需执行下面三个命令完成安装即可。

yum -y update
yum groupinstall "Development tools" -y
yum install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel -y

3、然后安装Python2.7.6。

cd /opt
wget --no-check-certificate https://www.python.org/ftp/python/2.7.6/Python-2.7.6.tar.xz
tar xf Python-2.7.6.tar.xz
./configure --prefix=/usr/local
sudo make && sudo make altinstall

4、查看是否安装Python2.7.6。

/usr/local/bin/python2.7 --version

5、移除老版本python2.6.6,然后创建2.7的软链接。之后可以直接使用Python -V可以看到Python版本已经是2.7.6。

mv /usr/bin/python /usr/bin/python2.6.6
ln -s /usr/local/bin/python2.7 /usr/bin/python
Python -V

6、修复兼容问题,升级Python版本到2.7.6后是无法直接执行yum安装软件的,需要修改yum兼容。请执行下面命令

vi /usr/bin/yum

编辑文件,在顶部修改成:

#!/usr/bin/python2.6.6

CentOS 7安装Python3.X

由于CentOS 7自带的Python是2.7,博主想用Python3.X的版本,考虑到卸载自带的2.7会带来诸多系统问题,于是就再编译安装一个Python3.X的版本了,与2.7版本共存,互不影响。

# 下载Python3.X安装包
[root@localhost Downloads]# wget https://www.python.org/ftp/python/3.6.2/Python-3.6.2.tar.xz
# 对于tar.xz文件,需要先用 xz -d xxx.tar.xz 将 xxx.tar.xz 解压成 xxx.tar 然后再用 tar xvf xxx.tar 来解压
[root@localhost Downloads]# xz -d Python-3.6.2.tar.xz
[root@localhost Downloads]# tar xvf Python-3.6.2.tar
# 解压后,在编译前需要安装Python依赖包,否则可能会出现编译错误,如"zipimport.ZipImportError"错误
[root@localhost Downloads]# yum install zlib zlib-devel -y
[root@localhost Python-3.6.2]# ./configure --prefix=/usr/local/python3.6
[root@localhost Python-3.6.2]# make
[root@localhost Python-3.6.2]# make install   # 如果这一步出现错误,再针对性解决,一般都是缺失依赖包导致的。
# 编译安装完成后,还需要将Python3.6添加至环境变量,便于全局使用
[root@localhost Python-3.6.2]# vim /etc/profile
export PATH=$PATH:/usr/local/python3.6/bin/  #新增内容,保存退出
[root@localhost Python-3.6.2]# source /etc/profile   #刷新环境变量
# 到这里,Python3.6已经安装完毕,并可正常使用。如下,系统默认Python是2.7
[root@localhost Downloads]# python
Python 2.7.5 (default, Nov  6 2016, 00:28:07) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-11)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> eixt()
# 退出后,我们进入python3.6
[root@localhost Downloads]# python3.6
Python 3.6.2 (default, Aug 24 2017, 14:25:11) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-11)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> exit()
[root@localhost Downloads]#

好了,到这里,就可以愉快的使用Python3.X了,暂时未发现问题,有问题会及时更新说明。

使用python删除大文件中的特定行的最优算法

问题

1.a
2.ab
3.bc

文件如上所示,数字为行数。比如我需要删除内容为”a”的行,在大文件情况下,用何种语法最优?
其他编程语言也可推荐,谢谢。

最佳答案

with open('file.txt', 'r') as old_file:
    with open('file.txt', 'r+') as new_file:

        # 定位到需要删除的行,跳出循环时,seek_point 存放的是被删除行的行首的光标位置
        while True:
            # 记录光标位置
            seek_point = old_file.tell()

            if 'a' in old_file.readline().strip().split('.'):
                # 光标跳到被删除行的下一行行首
                break

        # 设置光标位置,光标在被删除行的行首
        new_file.seek(seek_point, 0)

        # 被删除行的下一行读给 next_line
        next_line = old_file.readline()

        # 连续覆盖剩余行,后面所有行上移一行
        while next_line:
            new_file.write(next_line)
            next_line = old_file.readline()

        # 写完最后一行后截断文件,因为删除操作,文件整体少了一行,原文件最后一行需要去掉
        new_file.truncate()

使用virtualenv或pyenv构建python虚拟环境

有时候在安装python环境时会遇到一些奇葩的问题,比如有些包无论如何也安装不了,受限于python版本,有些环境部署实在麻烦。因此我建议使用虚拟环境来部署python,比如一个项目就单独建立一个python虚拟环境,与其他项目互不干扰。python虚拟环境工具很多,这里主要介绍virtualenv与pyenv。

virtualenv

virtualenv是跨平台的,linux、mac、windows都可以使用。

install

pip install virtualenv

创建虚拟目录

virtualenv kvenv -p /usr/bin/python2

说明:创建完成后会生成一个kvenv目录,可以加上-p参数指定Python版本。(当然要系统安装了某版本的python才能创建这个版本的虚拟目录)

激活虚拟环境

source kvenv/bin/activate

退出虚拟环境

deactivate

查看python路径

which python # 看python路径是否为新创建的虚拟目录

说明:Mac、linux与windows上安装使用方法一样。

pyenv

pyenv严格来说是python的版本控制器,使用很灵活。

Install

$ brew update
$ brew install peen

配置环境变量

$ echo 'eval "$(pyenv init -)"' >> ~/.bash_profile
source ~/.bash_profile

Usage

  • pyenv version # 当前版本
  • pyenv versions # 所有版本
  • pyenv global system # 全局切换
  • pyenv local 2.7.10 # 本地切换
  • pyenv local 3.5.0 –unset # 取消切换

pyenv常用命令

$ pyenv install --list #列出可安装版本
$ pyenv install <version> # 安装对应版本
$ pyenv versions # 显示当前使用的python版本
$ pyenv which python # 显示当前python安装路径
$ pyenv global <version> # 设置默认Python版本
$ pyenv local <version> # 当前路径创建一个.python-version, 以后进入这个目录自动切换为该版本
$ pyenv shell <version> # 当前shell的session中启用某版本,优先级高于global 及 local

安装其他版本python

pyenv install xx.xx.xx (pyenv install 3.4.3) #安装python3.4.3
pyenv rehash   # 安装完以后记得一定要rehash

virtualenv or pyenv ?

如果是项目环境,建议virtualenv,环境独立,也不会有很大的Bug。

如果只是个人学习练习python,可以使用pyenv,切换方便。

通过python和websocket构建实时通信系统[扩展saltstack监控]

先放一个小demo~

用html5的websocket实现的聊天平台。后端用的是python bottle框架。
后期要改成监控,可能要联合saltstack做实时的监控。
像上篇博客说的那样,实时监控就那点东西,就是接收数据、显示数据 。

像下面这样:

未分类

WebSocket API是下一代客户端-服务器的异步通信方法。该通信取代了单个的TCP套接字,使用ws或wss协议,可用于任意的客户端和服务器程序。WebSocket目前由W3C进行标准化。WebSocket已经受到Firefox 4、Chrome 、Opera 10.70以及Safari 5等浏览器的支持。

WebSocket API最伟大之处在于服务器和客户端可以在给定的时间范围内的任意时刻,相互推送信息。WebSocket并不限于以Ajax(或XHR)方式通信,因为Ajax技术需要客户端发起请求,而WebSocket服务器和客户端可以彼此相互推送信息;XHR受到域的限制,而WebSocket允许跨域通信。

WebSocket的优点

a)、服务器与客户端之间交换的标头信息很小,大概只有2字节;

b)、客户端与服务器都可以主动传送数据给对方;

c)、不用频率创建TCP请求及销毁请求,减少网络带宽资源的占用,同时也节省服务器资源;

建立连接的握手

当Web应用程序调用new WebSocket(url)接口时,Browser就开始了与地址为url的WebServer建立握手连接的过程。

  1. Browser与WebSocket服务器通过TCP三次握手建立连接,如果这个建立连接失败,那么后面的过程就不会执行,Web应用程序将收到错误消息通知。

  2. 在TCP建立连接成功后,Browser/UA通过http协议传送WebSocket支持的版本号,协议的字版本号,原始地址,主机地址等等一些列字段给服务器端。

  3. WebSocket服务器收到Browser/UA发送来的握手请求后,如果数据包数据和格式正确,客户端和服务器端的协议版本号匹配等等,就接受本次握手连接,并给出相应的数据回复,同样回复的数据包也是采用http协议传输。

  4. Browser收到服务器回复的数据包后,如果数据包内容、格式都没有问题的话,就表示本次连接成功,触发onopen消息,此时Web开发者就可以在此时通过send接口想服务器发送数据。否则,握手连接失败,Web应用程序会收到onerror消息,并且能知道连接失败的原因。
    这个握手很像HTTP,但是实际上却不是,它允许服务器以HTTP的方式解释一部分handshake的请求,然后切换为websocket
    数据传输
    WebScoket协议中,数据以帧序列的形式传输。

考虑到数据安全性,客户端向服务器传输的数据帧必须进行掩码处理。服务器若接收到未经过掩码处理的数据帧,则必须主动关闭连接。

服务器向客户端传输的数据帧一定不能进行掩码处理。客户端若接收到经过掩码处理的数据帧,则必须主动关闭连接。

针对上情况,发现错误的一方可向对方发送close帧(状态码是1002,表示协议错误),以关闭连接。

未分类

ws的连接状态:

GET /chat HTTP/1.1
Upgrade: WebSocket
Connection: Upgrade
Host: 66.xiaorui.cc:10000
Origin: http://66.xiaorui.cc
Cookie: somenterCookie

简单了解下接口方法和属性:

readyState表示连接有四种状态:

  • CONNECTING (0):表示还没建立连接;
  • OPEN (1): 已经建立连接,可以进行通讯;
  • CLOSING (2):通过关闭握手,正在关闭连接;
  • CLOSED (3):连接已经关闭或无法打开;

  • url是代表 WebSocket 服务器的网络地址,协议通常是”ws”或“wss(加密通信)”,send 方法就是发送数据到服务器端;

  • close 方法就是关闭连接;
  • onopen连接建立,即握手成功触发的事件;
  • onmessage收到服务器消息时触发的事件;
  • onerror异常触发的事件;
  • onclose关闭连接触发的事件;

来个例子,咱们用js来搞搞

var wsServer = 'ws://localhost:8888/Demo'; //服务器地址
var websocket = new WebSocket(wsServer); //创建WebSocket对象
websocket.send("hello");//向服务器发送消息
alert(websocket.readyState);//查看websocket当前状态
websocket.onopen = function (evt) {
    //已经建立连接
};
websocket.onclose = function (evt) {
    //已经关闭连接
};
websocket.onmessage = function (evt) {
    //收到服务器消息,使用evt.data提取
};
websocket.onerror = function (evt) {
    //产生异常
};

我的后端代码:

python的后端实现websocket的处理,有很多方法的。

比较常见的是 gevent的websocket的方式。

from bottle import get, run, template
from bottle.ext.websocket import GeventWebSocketServer
from bottle.ext.websocket import websocket
import gevent
users = set()
@get('/')
def index():
    return template('index')
@get('/websocket', apply=[websocket])
def chat(ws):
    users.add(ws)
    while True:
        msg = ws.receive()
        if msg is not None:
            for u in users:
                print type(u)
                u.send(msg)
                print u,msg
        else: break
    users.remove(ws)
run(host='10.10.10.66', port=10000, server=GeventWebSocketServer)

后端的东西比较的简单,就是把接收到的数据,原路打回去。。。

我前端的代码

这个是连接webscoket,然后接收和发数据的js

<script>
        $(document).ready(function() {
            if (!window.WebSocket) {
                if (window.MozWebSocket) {
                    window.WebSocket = window.MozWebSocket;
                } else {
                    $('#messages').append("<li>Your browser doesn't support WebSockets.</li>");
                }
            }
            ws = new WebSocket('ws://10.10.10.66:10000/websocket');
            ws.onopen = function(evt) {
                $('#messages').append('<li>Connected to chat.</li>');
            }
            ws.onmessage = function(evt) {
                $('#messages').append('<li>' + evt.data + '</li>');
            }
            $('#send-message').submit(function() {
                ws.send($('#name').val() + ": " + $('#message').val());
                $('#message').val('').focus();
                return false;
            });
        });
    </script>

用来呈现结果的div

form id="send-message" class="form-inline">
        <input id="name" type="text" value="可以更换名字">
        <input id="message" type="text" value="要扯淡的内容" />
         <button class="btn btn-success" type="submit">Send</button>
    </form>
    <div id="messages"></div>

这里有个tornado后端的代码,实现的过程和我差不多的~我需要的朋友可以跑一下~

import logging
import os.path
import uuid
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
import tornado.websocket
def send_message(message):
    for handler in ChatSocketHandler.socket_handlers:
        try:
            handler.write_message(message)
        except:
            logging.error('Error sending message', exc_info=True)
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.render('index.html')
class ChatSocketHandler(tornado.websocket.WebSocketHandler):
    socket_handlers = set()
    def open(self):
        ChatSocketHandler.socket_handlers.add(self)
        send_message('A new user has entered the chat room.')
    def on_close(self):
        ChatSocketHandler.socket_handlers.remove(self)
        send_message('A user has left the chat room.')
    def on_message(self, message):
        send_message(message)
def main():
    settings = {
        'template_path': os.path.join(os.path.dirname(__file__), 'templates'),
        'static_path': os.path.join(os.path.dirname(__file__), 'static')
    }
    application = tornado.web.Application([
        ('/', MainHandler),
        ('/new-msg/', ChatHandler),
        ('/new-msg/socket', ChatSocketHandler)
    ], **settings)
    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(8000)
    tornado.ioloop.IOLoop.instance().start()
if __name__ == '__main__':
    main()

我和沈灿的对话~

未分类

沈灿和我的对话

未分类

Python爬取糗事百科实践

大家好,前面入门已经说了那么多基础知识了,下面我们做几个实战项目来挑战一下吧。那么这次为大家带来,Python爬取糗事百科的小段子的例子。

首先,糗事百科大家都听说过吧?糗友们发的搞笑的段子一抓一大把,这次我们尝试一下用爬虫把他们抓取下来。

友情提示

糗事百科在前一段时间进行了改版,导致之前的代码没法用了,会导致无法输出和CPU占用过高的情况,是因为正则表达式没有匹配到的缘故。
现在,博主已经对程序进行了重新修改,代码亲测可用,包括截图和说明,之前一直在忙所以没有及时更新,望大家海涵!
更新时间:2015/8/2

糗事百科又又又又改版了,博主已经没心再去一次次匹配它了,如果大家遇到长时间运行不出结果也不报错的情况,请大家参考最新的评论,热心小伙伴提供的正则来修改下吧~
更新时间:2016/3/27

本篇目标

  • 抓取糗事百科热门段子
  • 过滤带有图片的段子
  • 实现每按一次回车显示一个段子的发布时间,发布人,段子内容,点赞数。

糗事百科是不需要登录的,所以也没必要用到Cookie,另外糗事百科有的段子是附图的,我们把图抓下来图片不便于显示,那么我们就尝试过滤掉有图的段子吧。

好,现在我们尝试抓取一下糗事百科的热门段子吧,每按下一次回车我们显示一个段子。

1. 确定URL并抓取页面代码

首先我们确定好页面的URL是 http://www.qiushibaike.com/hot/page/1,其中最后一个数字1代表页数,我们可以传入不同的值来获得某一页的段子内容。

我们初步构建如下的代码来打印页面代码内容试试看,先构造最基本的页面抓取方式,看看会不会成功

# -*- coding:utf-8 -*-
import urllib
import urllib2


page = 1
url = 'http://www.qiushibaike.com/hot/page/' + str(page)
try:
    request = urllib2.Request(url)
    response = urllib2.urlopen(request)
    print response.read()
except urllib2.URLError, e:
    if hasattr(e,"code"):
        print e.code
    if hasattr(e,"reason"):
        print e.reason

运行程序,哦不,它竟然报错了,真是时运不济,命途多舛啊

line 373, in _read_status
 raise BadStatusLine(line)
httplib.BadStatusLine: ''

好吧,应该是headers验证的问题,我们加上一个headers验证试试看吧,将代码修改如下

# -*- coding:utf-8 -*-
import urllib
import urllib2

page = 1
url = 'http://www.qiushibaike.com/hot/page/' + str(page)
user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
headers = { 'User-Agent' : user_agent }
try:
    request = urllib2.Request(url,headers = headers)
    response = urllib2.urlopen(request)
    print response.read()
except urllib2.URLError, e:
    if hasattr(e,"code"):
        print e.code
    if hasattr(e,"reason"):
        print e.reason

嘿嘿,这次运行终于正常了,打印出了第一页的HTML代码,大家可以运行下代码试试看。在这里运行结果太长就不贴了。

2. 提取某一页的所有段子

好,获取了HTML代码之后,我们开始分析怎样获取某一页的所有段子。

首先我们审查元素看一下,按浏览器的F12,截图如下

未分类

我们可以看到,每一个段子都是

包裹的内容。

现在我们想获取发布人,发布日期,段子内容,以及点赞的个数。不过另外注意的是,段子有些是带图片的,如果我们想在控制台显示图片是不现实的,所以我们直接把带有图片的段子给它剔除掉,只保存仅含文本的段子。

所以我们加入如下正则表达式来匹配一下,用到的方法是 re.findall 是找寻所有匹配的内容。方法的用法详情可以看前面说的正则表达式的介绍。

好,我们的正则表达式匹配语句书写如下,在原来的基础上追加如下代码

content = response.read().decode('utf-8')
pattern = re.compile('<div.*?author">.*?<a.*?<img.*?>(.*?)</a>.*?<div.*?'+
                         'content">(.*?)<!--(.*?)-->.*?</div>(.*?)<div class="stats.*?class="number">(.*?)</i>',re.S)
items = re.findall(pattern,content)
for item in items:
    print item[0],item[1],item[2],item[3],item[4]

现在正则表达式在这里稍作说明

  • .*? 是一个固定的搭配,.和代表可以匹配任意无限多个字符,加上?表示使用非贪婪模式进行匹配,也就是我们会尽可能短地做匹配,以后我们还会大量用到 .? 的搭配。

  • (.*?)代表一个分组,在这个正则表达式中我们匹配了五个分组,在后面的遍历item中,item[0]就代表第一个(.?)所指代的内容,item[1]就代表第二个(.?)所指代的内容,以此类推。

  • re.S 标志代表在匹配时为点任意匹配模式,点 . 也可以代表换行符。

这样我们就获取了发布人,发布时间,发布内容,附加图片以及点赞数。

在这里注意一下,我们要获取的内容如果是带有图片,直接输出出来比较繁琐,所以这里我们只获取不带图片的段子就好了。

所以,在这里我们就需要对带图片的段子进行过滤。

我们可以发现,带有图片的段子会带有类似下面的代码,而不带图片的则没有,所以,我们的正则表达式的item[3]就是获取了下面的内容,如果不带图片,item[3]获取的内容便是空。

<div class="thumb">

<a href="/article/112061287?list=hot&s=4794990" target="_blank">
<img src="http://pic.qiushibaike.com/system/pictures/11206/112061287/medium/app112061287.jpg" alt="但他们依然乐观">
</a>

</div>

所以我们只需要判断item[3]中是否含有img标签就可以了。

好,我们再把上述代码中的for循环改为下面的样子

for item in items:
        haveImg = re.search("img",item[3])
        if not haveImg:
            print item[0],item[1],item[2],item[4]

现在,整体的代码如下

# -*- coding:utf-8 -*-
import urllib
import urllib2
import re

page = 1
url = 'http://www.qiushibaike.com/hot/page/' + str(page)
user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
headers = { 'User-Agent' : user_agent }
try:
    request = urllib2.Request(url,headers = headers)
    response = urllib2.urlopen(request)
    content = response.read().decode('utf-8')
    pattern = re.compile('<div.*?author">.*?<a.*?<img.*?>(.*?)</a>.*?<div.*?'+
                         'content">(.*?)<!--(.*?)-->.*?</div>(.*?)<div class="stats.*?class="number">(.*?)</i>',re.S)
    items = re.findall(pattern,content)
    for item in items:
        haveImg = re.search("img",item[3])
        if not haveImg:
            print item[0],item[1],item[2],item[4]
except urllib2.URLError, e:
    if hasattr(e,"code"):
        print e.code
    if hasattr(e,"reason"):
        print e.reason

运行一下看下效果

未分类

恩,带有图片的段子已经被剔除啦。是不是很开森?

3. 完善交互,设计面向对象模式

好啦,现在最核心的部分我们已经完成啦,剩下的就是修一下边边角角的东西,我们想达到的目的是:

按下回车,读取一个段子,显示出段子的发布人,发布日期,内容以及点赞个数。

另外我们需要设计面向对象模式,引入类和方法,将代码做一下优化和封装,最后,我们的代码如下所示

__author__ = 'CQC'
# -*- coding:utf-8 -*-
import urllib
import urllib2
import re
import thread
import time

#糗事百科爬虫类
class QSBK:

    #初始化方法,定义一些变量
    def __init__(self):
        self.pageIndex = 1
        self.user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
        #初始化headers
        self.headers = { 'User-Agent' : self.user_agent }
        #存放段子的变量,每一个元素是每一页的段子们
        self.stories = []
        #存放程序是否继续运行的变量
        self.enable = False
    #传入某一页的索引获得页面代码
    def getPage(self,pageIndex):
        try:
            url = 'http://www.qiushibaike.com/hot/page/' + str(pageIndex)
            #构建请求的request
            request = urllib2.Request(url,headers = self.headers)
            #利用urlopen获取页面代码
            response = urllib2.urlopen(request)
            #将页面转化为UTF-8编码
            pageCode = response.read().decode('utf-8')
            return pageCode

        except urllib2.URLError, e:
            if hasattr(e,"reason"):
                print u"连接糗事百科失败,错误原因",e.reason
                return None


    #传入某一页代码,返回本页不带图片的段子列表
    def getPageItems(self,pageIndex):
        pageCode = self.getPage(pageIndex)
        if not pageCode:
            print "页面加载失败...."
            return None
        pattern = re.compile('<div.*?author">.*?<a.*?<img.*?>(.*?)</a>.*?<div.*?'+
                         'content">(.*?)<!--(.*?)-->.*?</div>(.*?)<div class="stats.*?class="number">(.*?)</i>',re.S)
        items = re.findall(pattern,pageCode)
        #用来存储每页的段子们
        pageStories = []
        #遍历正则表达式匹配的信息
        for item in items:
            #是否含有图片
            haveImg = re.search("img",item[3])
            #如果不含有图片,把它加入list中
            if not haveImg:
                replaceBR = re.compile('<br/>')
                text = re.sub(replaceBR,"n",item[1])
                #item[0]是一个段子的发布者,item[1]是内容,item[2]是发布时间,item[4]是点赞数
                pageStories.append([item[0].strip(),text.strip(),item[2].strip(),item[4].strip()])
        return pageStories

    #加载并提取页面的内容,加入到列表中
    def loadPage(self):
        #如果当前未看的页数少于2页,则加载新一页
        if self.enable == True:
            if len(self.stories) < 2:
                #获取新一页
                pageStories = self.getPageItems(self.pageIndex)
                #将该页的段子存放到全局list中
                if pageStories:
                    self.stories.append(pageStories)
                    #获取完之后页码索引加一,表示下次读取下一页
                    self.pageIndex += 1

    #调用该方法,每次敲回车打印输出一个段子
    def getOneStory(self,pageStories,page):
        #遍历一页的段子
        for story in pageStories:
            #等待用户输入
            input = raw_input()
            #每当输入回车一次,判断一下是否要加载新页面
            self.loadPage()
            #如果输入Q则程序结束
            if input == "Q":
                self.enable = False
                return
            print u"第%d页t发布人:%st发布时间:%st赞:%sn%s" %(page,story[0],story[2],story[3],story[1])

    #开始方法
    def start(self):
        print u"正在读取糗事百科,按回车查看新段子,Q退出"
        #使变量为True,程序可以正常运行
        self.enable = True
        #先加载一页内容
        self.loadPage()
        #局部变量,控制当前读到了第几页
        nowPage = 0
        while self.enable:
            if len(self.stories)>0:
                #从全局list中获取一页的段子
                pageStories = self.stories[0]
                #当前读到的页数加一
                nowPage += 1
                #将全局list中第一个元素删除,因为已经取出
                del self.stories[0]
                #输出该页的段子
                self.getOneStory(pageStories,nowPage)


spider = QSBK()
spider.start()

好啦,大家来测试一下吧,点一下回车会输出一个段子,包括发布人,发布时间,段子内容以及点赞数,是不是感觉爽爆了!

我们第一个爬虫实战项目介绍到这里,欢迎大家继续关注,小伙伴们加油!

使用进程池规避Python的GIL限制

  • 操作系统 : CentOS7.3.1611_x64

  • python版本:2.7.5

问题描述

Python的GIL会对CPU密集型的程序产生影响,如果完全使用Python来编程,怎么避开GIL的限制呢?

解决方案

在多线程中使用进程池来规避GIL的限制。具体如下:

1、使用multiprocessing模块来创建进程池;

2、将计算任务分配给不同的线程;

3、在任务线程中把任务提交给之前创建的进程池;

每当有线程要执行cpu密集型任务时,就把该任务提交到进程池中,然后进程池会将任务交给运行在另一个进程中的Python解释器。

当线程等待结果时会释放GIL,而此时的计算任务是在另一个单独的Python解释器中执行的,不再受到GIL的限制了。

在多核系统中采用这个方案能轻易地利用到所有的CPU核心。

假设有这样的worker函数:

def worker(arr):
    s = 0
    for n in arr :
        arrTmp = range(1,n+1)
        if len(arrTmp) == 0 : continue
        rtmp = 1
        for i in arrTmp :
            rtmp *= i
        s += rtmp
    return s

完整代码如下:https://github.com/mike-zhang/pyExamples/blob/master/gilAvoid/gilAvoidTest1/taskCommon.py

普通单进程任务实现:

def main():
    s = 0
    tStart,tStop = 1,1000
    for i in range(1,100):
        #t = worker(range(tStart,tStop))
        t = worker(range(1,1000))
        s += t
        tStart = tStop
        tStop += 1000
    print("len : ",len(str(s)))
    print(s%10000)

完整代码如下: https://github.com/mike-zhang/pyExamples/blob/master/gilAvoid/gilAvoidTest1/t1_normal.py

运行效果如下:

(py27env) [mike@localhost test]$ time python t1_normal.py
('len : ', 2567)
987

real    0m17.919s
user    0m17.915s
sys     0m0.003s

使用进程池的实现:

def wokerThread(start,stop):
    #r = gPool.apply(worker,(range(start,stop),))
    r = gPool.apply(worker,(range(1,1000),))
    q.put(r)

def main():
    s = 0
    thrdArr = []
    tStart,tStop = 1,1000
    for i in range(1,gCount+1):
        thrd = threading.Thread(target=wokerThread,args=(tStart,tStop))
        thrdArr.append(thrd)
        tStart = tStop
        tStop += 1000
    for t in thrdArr :
        t.daemon = True
        t.start()
    while not q.full(): time.sleep(0.1)
    while not q.empty(): s += q.get()
    print("len : ",len(str(s)))
    print(s%10000)

完整代码如下:https://github.com/mike-zhang/pyExamples/blob/master/gilAvoid/gilAvoidTest1/t2_mp.py

运行效果如下:

(py27env) [mike@localhost test]$ time python t2_mp.py
queue full
('len : ', 2567)
987

real    0m4.917s
user    0m18.356s
sys     0m0.146s

可以看出使用上述方法可以规避GIL的限制(测试机器为i5 4核),程序的速度得到明显的提升。

好,就这些了,希望对你有帮助。

本文github地址:

https://github.com/mike-zhang/mikeBlogEssays/blob/master/2017/20170819_使用进程池规避python的GIL限制.rst

Python服务端内存泄露的处理过程

吐槽

内存泄露 ? 内存暴涨 ? OOM ?

首先提一下我自己曾经历过多次内存泄露,到底有几次? 我自己心里悲伤的回想了下,造成线上影响的内存泄露事件有将近5次了,没上线就查出内存暴涨次数可能更多。这次不是最惨,相信也不会是最后的内存的泄露。

有人说,内存泄露对于程序员来说,是个好事,也是个坏事。 怎么说? 好事在于,技术又有所长进,经验有所心得…. 毕竟不是所有程序员都写过OOM的服务…. 坏事当然就是被人吐槽了…. 回想了下被OOM的服务,并发和数据量级都相对大,也就是说,并发量大的服务容易出现OOM…

说的自己都乐了,为毛每次写服务端和客户端的时候,我很大几率会出现内存泄露呢…. 一路走来发现内存暴涨的原因,一般是数据的边界条件处理不合理导致的,而单纯因为某个模块及框架bug代码导致的内存泄露相对少。

废话不多说,简单说下这次的处理过程.

ops的同事给我打电话,说我的服务挂了… 作为肇事者的我说,不应该呀,你再启动下呀? 那哥们说,启动了,一会又死了… 我说,再启动下呀? 一会ops回复说, 又死了… 卧槽呀,这不是个好现象呀….

没办法,哥只能亲自上了…. 首先确认下是什么原因导致的程序总是挂掉, 因为我的服务端是Master Worker设计模式,在顶级的函数入口做了try catch,协程入口也做了try catch,所以程序的逻辑异常肯定不会导致这个进程崩溃的。进程的日志没有特别明显的异常信息…

怀疑是被oom了,dmesg看到的信息果然是被oom了… 那么这里有几个问题:

  • 为什么会被oom

  • 什么时候被oom的,内存持续了多久?

  • oom应该干掉泄露内存的worker进程,为什么把 worker 和 feeds进程都干掉了…

然后我们开始确认下内存的增长趋势,及相关的监控图表。 通过监控服务器的内存图表,我们得知,内存的增长是爆发性的,后期根据我自己的内存监控脚本显示,从500m内存 干到 16G,再到被内核OOM,最快时候一分钟完事。

另外通过流量图表,我们可以找到其影响关系,没次内存暴涨的时候,流量也很高…. 但根据我程序的计算显示qps并不高,但是内存暴增的厉害,流量图徒增的也很厉害… 我这个服务是做全网cdn刷新预缓存的业务,操作cdn只是触发header请求而已,按照道理不会有太大的流量。

这内存暴增的问题不是时常出现的,有可能几天才出现一次。 在内存暴增之后,追问题其实相对麻烦的,只能看日志。 最简单直接的方法是,事故发生时,在线调….. (这需要我时刻关注监控服务情况,第一时间做在线追问题)….

服务上线之前,我们肯定经过疯狂的压力和稳定性测试,但是从来没有出现过内存泄露问题,很是疑惑的…

当遇到问题时,我先通过strace看看有什么不良的系统调用,发现有大量的recvfrom… recvfrom系统调用是用来从socket读取数据的… 为什么会有大量的数据recv呢 ? tcpdump抓包分析出有大量的数据是二进制的…. 但我的服务只是发送http header请求呀…. 通过不断的抓包和iftop流量分析,得出 http get 请求大文件导致的内存暴增问题…

# xiaorui.cc

% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 99.56    1.158373           6    189545           recvfrom
  0.44    0.005162           0     18301           brk
  0.00    0.000000           0        23           read
  0.00    0.000000           0        21           write
  0.00    0.000000           0         6           open
  0.00    0.000000           0         8           close
  0.00    0.000000           0         6           fstat
  0.00    0.000000           0        10         3 mmap

分析数据得知,明显是二进制…. 正常接口不会出现二进制的数据流…

# xiaorui.cc

recvfrom(3, "207343c320fD2014264363rE^34350a;177322222[H`n364302315!=247 "..., 10240, 0, NULL, NULL) = 3358
recvfrom(3, "R,C45c23M30522531&327136424422536204302304D237240367315`331]e234345"..., 6882, 0, NULL, NULL) = 6882
recvfrom(3, "207S3312252243649202326[ 375362"N30133420F332f234"2706321q361207wv"..., 10240, 0, NULL, NULL) = 10240
recvfrom(3, "214H303A*377I,256221316gF4244274234t260265217317<r307<G=255A30246"..., 10240, 0, NULL, NULL) = 10240
recvfrom(3, "zT31235317722*q26326tq341274236323<Cb34510341NA}7252222c203%"..., 10240, 0, NULL, NULL) = 6218
recvfrom(3, "22425241W$F233317350L347244361[271262[^347274333221ng36s266324313Ug275"..., 4022, 0, NULL, NULL) = 4022
recvfrom(3, "U341310210]w250C%2330375364R274372Z726334026331325302224376300260zPy271"..., 10240, 0, NULL, NULL) = 10240

我们知道大多数cdn厂商给我们开放了不同的http method方法,每个方法有不同的意思…. 除了get请求外,大多请求都是异步的…. 但是get是同步的, 既然是同步的,那么你直接用python requests访问一个大文件,必然是需要把数据载入到内存中…. 对的,内存中…. 就算你只是想看看response的http code,但对于一些http client module来说,还是需要加载完所有buffer才会解析成header….

那么怎么解决? requests 针对一些大文件的get请求 改成 写文件的方式请求… 这样就避免了,你请求了 1G 大小的文件,必须拿到所有数据后,才可以return的逻辑… 你把实时recv到内存,改成append到文件里…. 但这样就好了么? 不,还是会有内存的问题… 当然相比直接gevent requests get 好不少….. 另外,我最后加了手动触发gc,这样内存能得到及时释放,我们知道python gc 是有thold刷新策略的。

我最后的解决方式是什么? 我是采用gevent subprocess调用curl来处理大文件…. 为什么用这么粗暴的方式来系统调用curl来处理…

curl是 c 写的,性能极棒 !!! 针对大文件请求,cpu消耗同比python requests少的多… 有些人说了,处理大文件的请求明显是 io bound,怎么可能浪费cpu呢,这也没有啥cpu bound密集活动呀…. 我们多进程加多协程的框架,协程之间的调度需要资源,http parser需要资源…. requests库纯py写的http parser…. 你访问一两个文件肯定没问题了,但是你高并发去处理这类请求,你python requests总是占用cpu时间,那自然就cpu占用率高了,你高了,自然别人就拿不到时间片调度了,只能等了….. 大家可以做一些量级的测试对比….

未分类

为什么某个子进程被oom了,父进程也跟着退出,而且其他进程是友好退出…. 所谓的友好的退出意思是,每个进程也都有任务队列缓冲,当得知要退出时,他会把队列中的数据退还给redis,并且等待正在执行的任务完成后才退出….

问题在于我在父进程做了很多信号的处理,子进程当然继承了父进程的信号处理,当获取不到内存时,会触发SIGINT信号,对的…. 首先是SIGINT,然后才有SIGKILL ( -9 ). 当我收到sigint时,会给一个共享变量配置一个标记,所以其他进程会出现安全退出的情况….

rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x33efc0f7e0}, {0x4e7de0, [], SA_RESTORER, 0x33efc0f7e0}, 8) = 0
+++ exited with 1 +++

但可能当我自己还没认识到内存不够用,有人向内核申请内存,内核说没有,但是我可以干掉分值最低,占内存最大的进程…. 这就造成了OOM….. sigkill是无法捕获的….

针对子进程由于copy on write继承父进程的信号配置,我们可以在子进程fork之后,再重新初始化信号配置… 这样子进程不管什么原因退出,都不管影响到master主进程, 主进程会定时做子进程轮询,如果某子进程挂了,他会重置一个新内存放在进程池里面…. 这方面借鉴了uwsgi的设计…

END…

Python使用os、try或pathlib判断文件是否存在

通常在读写文件之前,需要判断文件或目录是否存在,不然某些处理方法可能会使程序出错。所以最好在做任何操作之前,先判断文件是否存在。

这里将介绍三种判断文件或文件夹是否存在的方法,分别使用os模块、Try语句、pathlib模块。

1. 使用os模块

os模块中的os.path.exists()方法用于检验文件是否存在。

  • 判断文件是否存在
import os
os.path.exists(test_file.txt)
#True

os.path.exists(no_exist_file.txt)
#False
  • 判断文件夹是否存在
import os
os.path.exists(test_dir)
#True

os.path.exists(no_exist_dir)
#False

可以看出用os.path.exists()方法,判断文件和文件夹是一样。

其实这种方法还是有个问题,假设你想检查文件“test_data”是否存在,但是当前路径下有个叫“test_data”的文件夹,这样就可能出现误判。为了避免这样的情况,可以这样:

  • 只检查文件
import os
os.path.isfile("test-data")

通过这个方法,如果文件”test-data”不存在将返回False,反之返回True。

即是文件存在,你可能还需要判断文件是否可进行读写操作。

判断文件是否可做读写操作

使用os.access()方法判断文件是否可进行读写操作。

语法:

os.access(path, mode)

path为文件路径,mode为操作模式,有这么几种:

  • os.F_OK: 检查文件是否存在;
  • os.R_OK: 检查文件是否可读;
  • os.W_OK: 检查文件是否可以写入;
  • os.X_OK: 检查文件是否可以执行

该方法通过判断文件路径是否存在和各种访问模式的权限返回True或者False。

import os
if os.access("/file/path/foo.txt", os.F_OK):
    print "Given file path is exist."

if os.access("/file/path/foo.txt", os.R_OK):
    print "File is accessible to read"

if os.access("/file/path/foo.txt", os.W_OK):
    print "File is accessible to write"

if os.access("/file/path/foo.txt", os.X_OK):
    print "File is accessible to execute"

2. 使用Try语句

可以在程序中直接使用open()方法来检查文件是否存在和可读写。

语法:

open(<file/path>)

如果你open的文件不存在,程序会抛出错误,使用try语句来捕获这个错误。

程序无法访问文件,可能有很多原因:

  • 如果你open的文件不存在,将抛出一个FileNotFoundError的异常;
  • 文件存在,但是没有权限访问,会抛出一个PersmissionError的异常。

所以可以使用下面的代码来判断文件是否存在:

try:
    f =open()
    f.close()
except FileNotFoundError:
    print "File is not found."
except PersmissionError:
    print "You don't have permission to access this file."

其实没有必要去这么细致的处理每个异常,上面的这两个异常都是IOError的子类。所以可以将程序简化一下:

try:
    f =open()
    f.close()
except IOError:
    print "File is not accessible."

使用try语句进行判断,处理所有异常非常简单和优雅的。而且相比其他不需要引入其他外部模块。

3. 使用pathlib模块

pathlib模块在Python3版本中是内建模块,但是在Python2中是需要单独安装三方模块。

使用pathlib需要先使用文件路径来创建path对象。此路径可以是文件名或目录路径。

  • 检查路径是否存在
path = pathlib.Path("path/file")
path.exist()
  • 检查路径是否是文件
path = pathlib.Path("path/file")
path.is_file()