Python3.5多线程爬虫越爬越慢的解决方法

系统环境

Ubuntu 16.04 Server
Python3.5

爬虫情况

1、从Mysql数据库获取任务
2、任务导入列表后开始http请求,将数据以文件形式保存到硬盘
3、开80线程

遇到的问题

1、家用路由器频繁死机(一天两三次)
2、爬虫开始时爬取速度很快,但是越来越慢

解决思路

查看爬虫日志发现路由器死机前,爬虫速度基本都在峰值,看来是路由器是累晕的,爬虫开始时速度快然后越来越慢,说明爬虫本身问题不大,应该是运行后消耗的系统资源越来越多,直到消耗殆尽而导致速度上不去。查看内存和cpu的消耗情况发现,在爬虫速度降低之后内存cpu的占用也有降低,看来问题不在硬件资源,那是不是网络资源内,使用netstat命令查看发现有大量“TIME_WAIT”状态的TCP连接,使用命令

netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

查看发现TIME_WAIT状态的连接近2w,看来是资源tcp连接过多!此时想是不是爬虫http头部信息中Connection的问题呢,把Keep-Alive改为close并未解决问题!那Ubuntu有没有设置tcp连接控制方面的选项呢?通过搜索得知,tcp连接可以设置回收时间和是否可以复用,命令如下:

echo "1" > /proc/sys/net/ipv4/tcp_tw_reuse
#设置TIME_WAIT状态可以重用
echo "1" > /proc/sys/net/ipv4/tcp_tw_recycle
#设置TCP连接尽快回收

使用以上命令后问题果然解决!

CentOS 6.5升级Python版本 修复yum和安装模块

CentOS python版本是V2.6.6,升级3.4.3。

  • 下载 安装包
wget http://www.python.org/ftp/python/3.4.3/Python-3.4.3.tgz
  • 解压安装包
tar -zxvf Python-3.4.3.tgz  
  • 进入解压后目录
cd Python-3.4.3   
  • 编译安装
./configure --prefix=/usr/local/python3.3
make && make install
  • 此时已完成新版本的安装,但由于老版本还在系统中,所以需要将原来/usr
    /bin/python链接改为新的连接

a.先修改老的连接,执行

mv /usr/bin/python /usr/bin/python2.6

b.再建立新连接

ln -s /usr/local/python3.3/bin/python3.3 /usr/bin/python
  • 查询python版本
python

解决升级后YUM无法使用

  • 打开/usr/bin/yum
vim /usr/bin/yum

将#!/usr/bin/python 修改为 #!/usr/bin/python2.6,保存退出

  • 测试是否修复
yum list  

使用easy_install和 pip 安装模块

  • 一些依赖
yum install mysql-devel zlib-devel gcc python-devel gcc libffi-devel python-devel openssl-devel readline-devel patch
  • A: yum 安装 easy_install
yum install python-setuptools
  • B: 通过ezsetup.py安装easyinstall
wget --no-check-certificate https://bootstrap.pypa.io/ez_setup.py
python ez_setup.py --insecure
  • 举个例子:安装模块paramiko,以下两种方法都可以
easy_install paramiko
pip install paramiko
  • 安装带setup.py的多文件模块包,下载后,解压,进入目录
python setup.py install

Centos7安装配置Python3.6 Django virtualenv gunicorn supervisor环境

跟着网上的教程走发现行不通阿!好多都是写个大概,而且每人的环境都是有些许差异的,比如说权限问题阿,等等都会造成安装的失败

说明:本教程在你已经拥有Centos7系统,已经安装好nginx服务器,已经安装了Python3.6 Django virtualenv gunicorn supervisor的前提下进行

接下来开始了!

1、新建你的django项目,假设项目名为Hello

 django-admin.py startproject Hello

2、想好你需要的端口号,假设端口号为8001(下面的端口号均以8001来举例,你可以换成你所需要的端口号),接下来启动服务器看看能不能运行,分两种情况

2.1 如果你只是想在本地运行则

python manage.py runserver127.0.0.1:8001

2.2 如果你想要外网也可以访问则

python manage.py runserver0.0.0.0:8001

3、接下来在浏览器中输入 “服务器ip:8001” ,比如我服务器的公网IP为 192.163.189.166 则输入192.163.189.166:8001,可能会出现三种情况!

3.1 成功运行

3.2 出现 DisallowedHost at / Invalid HTTP_HOST header: ‘10.211.55.6:8001’. You may need to add u’10.211.55.6′ to ALLOWED_HOSTS. 类似错误,解决方法:
进入项目目录下的Hello目录(注意项目目录名是和该名称相同的,此Hello和manage.py同级打开setting.py将ALLOWED_HOSTS = []改为ALLOWED_HOSTS = [‘*’]

3.3 如果在确保地址输入正确,端口也正确的前提下浏览器出现了 Unable to connect 错误,那么很可能是你的Centos7没有开启8001端口号的原因,解决方法

开启端口号

firewall-cmd --zone=public --add-port=8001/tcp --permanent (--permanent意思是永久生效,重启后继续生效)

重启防火墙

firewall-cmd --reload

此时再访问浏览器,如果还是访问不了,那可能是我没遇到的情况,还请自行搜索解决哦

4、配置virtualenv gunicorn

4.1 在项目根目录下输入指令 virtualenv venv (venv可以是其他名字了)

4.2 虚拟环境生成后接着要在虚拟环境中安装django 和 gunicorn 了

pip install django
pip install gunicorn

4.3 在项目根目录下创建gunicorn.conf 用来配置gunicorn,我的配置为

workers = 4
bind = '0.0.0.0:8088'

5、配置supervisor

supervisor的配置文件一般在/etc/supervisord.conf

5.1 vim /etc/supervisord.conf

5.2 在末尾加入

[program:hello]
command=/项目路径/venv/bin/gunicorn -c /项目路径/gunicorn.conf Hello.wsgi:application
directory=/项目路径
autostart=true
autorestart=true
stdout_logfile=/项目路径/logs/gunicorn.log
stderr_logfile=/项目路径/logs/gunicorn.err  

5.3 重启 supervisor

unlink /tmp/supervisor.sock
supervisord -c /etc/supervisord.conf

6、配置nignx

6.1 打开nignx.conf

6.2 在合适地方加入

location /  {
     proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
     proxy_set_header Host $http_host;
     proxy_redirect off;
     proxy_pass http://192.163.189.166:8001;   #http://外网ip:8001,如果是本机访问则
http://127.0.0.1:8001
              }

6.3 重启nginx

systemctl restart nginx

7、好啦,接下来在浏览器中输入 http://192.163.189.166:8001 应该能访问咯。

使用python的pyenv工具管理python virtualenv虚拟环境

pyenv安装

1. 安装依赖包

yum -y install git gcc make patch zlib-devel gdbm-devel openssl-devel sqlite-devel bzip2-devel readline-devel

2. 安装pyenv

curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | bash

3. 设置环境变量

echo >> .bash_profile << EOF
# pyenv settings
export PATH="~/.pyenv/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"
EOF

4. 使之生效

. .bash_profile或者source .bash_profile

这时候pyenv就可以使用了

pyenv使用指南

1、pyenv versions查看系统的上安装的Python版本。

其中前面的*表示当前工作目录正在使用的版本,其中 的 system表示系统自带的 Python 版本:

$ pyenv versions
* system (set by /Users/yulongjun/.pyenv/version)

2、pyenv install 安装其他版本的Python。
例如安装3.6.1和2.7.13版本:

$ pyenv install 3.6.1   # 安装3.6.1版本的Python
$ pyenv install 2.7.13  # 安装2.7.13版本的Python
$ pyenv versions        # 可以看到3个版本,system为系统自带的版本
* system (set by /usr/local/var/pyenv/version)
  2.7.13
  3.6.1
$ cd                   # 到家目录
$ mkdir Python36       # 创建Python3.6的工作目录
$ cd Python36
$ pyenv local 3.6.1    # 使当前工作目录使用Python3.6.1版本
$ python -V            # 查看一下当前目录用Python的版本,确实是3.6.1
Python3.6.1
$ pip -V               # 查看一下pip版本,是3.6的pip
pip 9.0.1 from /usr/local/var/pyenv/versions/3.6.1/lib/python3.6/site-packages 
$ cd                   # 回到家目录
$ mkdir Python27       # 创建python2.7的工作目录
$ cd Python27
$ pyenv local 2.7.13   # 使当前工作目录使用Python2.7.13版本
$ python -V            # 查看一下当前目录用Python的版本,确实是2.7.13
Python 2.7.13
$ pip -V               # 查看一下pip版本,是2.7的pip
pip 9.0.1 from /usr/local/var/pyenv/versions/2.7.13/lib/python2.7/site-packages (python 2.7)

pyenv-virtualenv的使用方法

pyenv-virtualenv是用来创建一个干净的虚拟Python环境的命令,通常在创建干净的新项目时候使用。使用方法如下:

1、创建虚拟环境–pyenv virtualenv 版本号 虚拟环境名。

$ pyenv virtualenv 3.6.1 venv-3.6.1

2、创建项目,让项目使用干净的Python3.6.1的虚拟环境:

[yulongjun@yulongjun ~]$ mkdir Learning-Python3
[yulongjun@yulongjun ~]$ cd Learning-Python3/
[yulongjun@yulongjun Learning-Python3]$ pyenv local venv-3.6.1
(venv-3.6.1) [yulongjun@yulongjun Learning-Python3]$ cd ..
[yulongjun@yulongjun ~]$ cd Learning-Python3/
(venv-3.6.1) [yulongjun@yulongjun Learning-Python3]$

我们会发现:只要我们进入Learning-Python3目录,就会自动激活virtualenv,退出Learning-Python3目录,就会关闭virtualenv。

如果要关闭自动激活,可以运行命令pyenv deactivate,要重新启用的话,运行pyenv activate 虚拟环境名。

ubuntu系统安装virtualenv python版本管理工具

virtualenv的官网在http://www.virtualenv.org/en/latest/
如其官方所说,virtualenv 是一个创建独立python环境的工具。其要解决的最基本问题就是库的依赖和版本,以及间接权限(indirectly permisions,没太明白)。

安装virtualenv,在默认的python2下的pip就行:

sudo apt-get install python-virtualenv

创建虚拟环境:

virtualenv -p /usr/bin/python3 py3env

激活虚拟环境:

source py3env/bin/activate

你会注意到shell的提示符行前多了(py3env)字样,这样你就可以放心的使用python3做开发了。先下载个三方库试试吧

pip install httplib2

大功告成了!

如果要退出python3虚拟环境,输入命令

deactivate

python使用sqlalchemy连接mysql数据库

sqlalchemy是python当中比较出名的orm程序。

什么是orm?

orm英文全称object relational mapping,就是对象映射关系程序,简单来说我们类似python这种面向对象的程序来说一切皆对象,但是我们使用的数据库却都是关系型的,为了保证一致的使用习惯,通过orm将编程语言的对象模型和数据库的关系模型建立映射关系,这样我们在使用编程语言对数据库进行操作的时候可以直接使用编程语言的对象模型进行操作就可以了,而不用直接使用sql语言。

什么是sqlalchemy?

sqlalchemy是python的orm程序,在整个python界当中相当出名。

安装sqlalchemy

在使用sqlalchemy之前要先给python安装mysql驱动,由于我使用的是python3原来的mysqldb不可用,所以这里推荐使用pymysql。
我们通过pip进行安装,在windows下使用pip安装包的时候要记得使用管理员身份运行cmd不然有些操作是无法进行的。

pip install pymysql

安装完以后安装再安装sqlalchemy

pip install sqlalchemy

如何使用sqlalchemy连接mysql?

通过import导入必要的包

from sqlalchemy import create_engine,Table,Column,Integer,String,MetaData,ForeignKey

创建一个连接引擎

engine=create_engine("mysql+pymysql://root:a5230411@localhost:3306/test",echo=True)

我们将连接引擎放到engine里面方便后面使用。
create_engine(“数据库类型+数据库驱动://数据库用户名:数据库密码@IP地址:端口/数据库”,其他参数)
上文当中echo=True是开启调试,这样当我们执行文件的时候会提示相应的文字。

创建元数据

什么是元数据?元数据就是描述数据的数据,举个简单的例子,小明身高170cm,体重50kg,性别男。其中身高,体重,性别就是元数据。当我们创建好连接引擎以后可以通过这个引擎抓取元数据。

metadata=MetaData(engine)

通过MetaData()方法创建了metadata实例,在这个方法里面带上engine的目的是绑定要连接引擎,当我们对这个metadata实例进行操作的时候就会直接连接到数据库。

添加表结构

设定好连接引擎和元数据,让我们向mysql里面创建表结构来进行测试。

user=Table('user',metadata,
    Column('id',Integer,primary_key=True),
    Column('name',String(20)),
    Column('fullname',String(40)),
    )
address_table = Table('address', metadata,
    Column('id', Integer, primary_key=True),
    Column('user_id', None, ForeignKey('user.id')),
    Column('email', String(128), nullable=False)
    )

其中Table()方法用来创建表,第一个参数为表明,第二是存入元数据,后面的参数使用Column()方法将数据库当中每一个字段的数据参数设置好。

执行创建

metadata.create_all()

因为已将将表结构存到了metadata里面,然后让metadata执行create_all()方法,这样就向数据库里创建了user和address表。

完成代码

from sqlalchemy import create_engine,Table,Column,Integer,String,MetaData,ForeignKey
engine=create_engine("mysql+pymysql://root:a5230411@localhost:3306/test",echo=True)
metadata=MetaData(engine)

user=Table('user',metadata,
    Column('id',Integer,primary_key=True),
    Column('name',String(20)),
    Column('fullname',String(40)),
    )
address_table = Table('address', metadata,
    Column('id', Integer, primary_key=True),
    Column('user_id', None, ForeignKey('user.id')),
    Column('email', String(128), nullable=False)
    )

metadata.create_all()

docker-compose快速搭建python开发环境

Docker提供了容器级别的资源隔离。由于Python的外部依赖管理中存在的问题,我们通常会使用virtualenv来对不同的项目创建其唯一的依赖环境。这时利用Docker进行Python开发,可以轻松解决不同Python项目之间的依赖隔离问题。

作为应用程序,我们通常需要依赖于多种外部服务,比如数据库、缓存服务等等。Docker-compose就是在Docker容器的基础上,提供了统一的容器编排语言,可以让你更轻松的利用Docker构建你的应用环境。

编写Dockerfile

我们使用requirements.txt定义我们的第三方python包依赖
Python
Project-Root
|– static
|– templates
|– server.py
|– requirements.txt
|– Dockerfile
|– docker-compose.yml

编写Dockerfile内容如下:
Python
在Dockerfile中,我们主要目的:通过requirements.txt文件安装第三方的Python库依赖;利用Docker的容器隔离,可以忽略掉很多在本地开发中需要使用的东西,比如virtualenv。

编排我们的Docker容器

在案例中,应用程序依赖了mongodb作为数据存储服务,以及redis作为缓存服务。在一般情况下,作为开发团队要么我们搭建统一的mongodb;要不就每个人在开发机上单独部署。

而在Docker中,我们则不在需要做这么多无用的事情。 Docker官方提供了大量的基础容器,基本涵盖了日常开发中我们需要的大部分依赖。 在https://hub.docker.com/我们可以搜索到我们需要的基础镜像。

比如mongodb以及redis,在docker-hub上官方都提供了容器话的服务。

以redis容器为例,我们在本地搭建redis服务要做的事情主要包括两步:
Python
这个时候我们就可以通过访问0.0.0.0:63775来访问我们的redis服务器了。

我们也可以通过Docker原生的命令来连接我们的应用容器和redis容器,以使我们的代码能够正常的访问redis服务
Python
而事实上,我们可以使用更加简化的方式来定义我们的容器组合管理,使用Docker-compose(前身Fig)来定义我们的容器组合关系。
Python
这里我们定义了3个容器web、redis、mongo。 其中,web容器是通过当前目录的Dockerfile进行构建,同时将当前目录挂在到/app目录。 而redis和mongo则直接使用官方进行。

通过使用links,我们可以在web容器中通过 ‘redis:6375’以及’mongo:21707’直接访问相应的服务。

开始Coding吧

Python
Docker会根据当前的目录下得Dockerfile构建基础镜像,并且使用python server.py运行程序,并且运行redis以及mongo服务。

同时由于使用了volumes挂载了本地目录到/app,此时如果我们是开启的Debug模式,我们就可以直接在本地使用你喜欢的文本编辑器去编写代码,并且更新的代码能够实时被重新加载。

当然在使用Docker中最漫长的过程就是,下镜像,下镜像&下镜像。
Python

利用python探测谷歌搜索可用IP

原理是查询_netblocks.google.com域名的TXT记录,这个记录有大量网段的谷歌IP,再探测443端口开放的IP。不过探测出开放443端口的IP后,可能还要使用curl来检测是不是谷歌搜索的服务器。这一步需要与443端口ssl握手,但验证证书是否一致,使用python暂时写不出来,可以用curl https://www.google.com –resolve www.google.com:443:1.2.3.4,其中1.2.3.4为要探测的谷歌IP。
python脚本:

#!/usr/bin/python
# -*- coding:utf-8 -*-
'''
install modules:
pip install dnspython
'''

import dns.resolver
import struct, socket
import re
import sys
import threading
import Queue

threadLock = threading.Lock()
SHARE_Q = Queue.Queue()  
_WORKER_THREAD_NUM = 10
GLOBAL_COUNTER = 0

class MyThread(threading.Thread) :

    def __init__(self, func) :
        super(MyThread, self).__init__()
        self.func = func

    def run(self) :
        self.func()


def worker() :
    global SHARE_Q
    global GLOBAL_COUNTER
    while not SHARE_Q.empty():
        item = SHARE_Q.get()

        if check_port(item):
            with threadLock:
                print(item)
                GLOBAL_COUNTER += 1
                if GLOBAL_COUNTER >= 100:
                    sys.exit(0)

def get_txt_record(domain):
    answers = dns.resolver.query(domain, 'TXT')
    for rdata in answers:
        return str(rdata)


def get_ip_range_from_txt_record(txt_record):
    ip_range = []
    re_ret = re.findall(r'ip4:([^ ]+)', txt_record)
    for ip_mask in re_ret:
        ip_range.append(ip_mask)

    return ip_range

def get_ip_from_cidr(ip_range):
    ips = []
    for ip_mask in ip_range:
        (ip, cidr) = ip_mask.split('/')
        cidr = int(cidr) 
        host_bits = 32 - cidr
        i = struct.unpack('>I', socket.inet_aton(ip))[0] # note the endianness
        start = (i >> host_bits) << host_bits # clear the host bits
        end = i | ((1 << host_bits) - 1) 

        for i in range(start, end):
            ips.append(socket.inet_ntoa(struct.pack('>I',i)))

    return ips

def check_port(address, port=443):
    s=socket.socket()
    s.settimeout(1)  
    try:
        s.connect((address,port))
        return True
    except socket.error,e:
        return False

def main():
    txt_record = get_txt_record("_netblocks.google.com")
    ip_range = get_ip_range_from_txt_record(txt_record)
    ips = get_ip_from_cidr(ip_range)

    global SHARE_Q
    threads = []
    for task in ips :  
        SHARE_Q.put(task)

    for i in xrange(_WORKER_THREAD_NUM) :
        thread = MyThread(worker)
        thread.start()
        threads.append(thread)
    for thread in threads :
        thread.join()


if __name__ == '__main__':
    main()

shell脚本:


while read ip;do if curl -s -m 3 https://www.google.com.hk --resolve www.google.com.hk:443:$ip -o /dev/null;then echo $ip fi done < ip.txt

cx_Freeze给python(pyqt)程序打包成exe过程

  1. 1、下载cx_frezze,安装。安装后会在 python目录 D:Python27Libsite-packagescx_FreezesamplesPyQt4 下有一个 setup.py
  2.  
  3. 2、把这个setup.py拷贝到需要打包的pyqt程序所在的目录,假设要打包的pyqt程序叫 main_window.py
  4.  
  5. 3、修改setup.py,将默认的“PyQt4app.py"替换成“main_window.py”
  6.  
  7. 4、cmd 切换到当前路径,运行 python setup.py build 即可
  1. BTW:今天打包了用pyqt写的版本发布工具,打包后运行程序出现"driver not loaded"
  2. 解决方法如下:
  3. 在程序的根目录新建子目录"sqldrivers",到C:Python27Libsite-packagesPyQt4pluginssqldrivers复制所需驱动到此目录,比如我用的是mysql,所以就复制了qsqlmysql4.dll。

转自:oldman的博客