python 多线程理解(二)

之前讲了多线程的一篇博客,感觉讲的意犹未尽,其实,多线程非常有意思。因为我们在使用电脑的过程中无时无刻都在多进程和多线程。我们可以接着之前的例子继续讲。请先看我的上一篇博客。
从上面例子中发现线程的创建是颇为麻烦的,每创建一个线程都需要创建一个tx(t1、t2、…),如果创建的线程多时候这样极其不方便。下面对通过例子进行继续改进:

player.py
复制代码

#coding=utf-8
from time import sleep, ctime
import threading

def muisc(func):
for i in range(2):
print ‘Start playing: %s! %s’ %(func,ctime())
sleep(2)

def move(func):
for i in range(2):
print ‘Start playing: %s! %s’ %(func,ctime())
sleep(5)

def player(name):
r = name.split(‘.’)[1]
if r == ‘mp3’:
muisc(name)
else:
if r == ‘mp4’:
move(name)
else:
print ‘error: The format is not recognized!’

list = [‘爱情买卖.mp3′,’阿凡达.mp4’]

threads = []
files = range(len(list))

#创建线程
for i in files:
t = threading.Thread(target=player,args=(list[i],))
threads.append(t)

if __name__ == ‘__main__’:
#启动线程
for i in files:
threads[i].start()
 for i in files:
   threads[i].join()

#主线程
print ‘end:%s’ %ctime()

复制代码

  有趣的是我们又创建了一个player()函数,这个函数用于判断播放文件的类型。如果是mp3格式的,我们将调用music()函数,如果是mp4格式的我们调用move()函数。哪果两种格式都不是那么只能告诉用户你所提供有文件我播放不了。

  然后,我们创建了一个list的文件列表,注意为文件加上后缀名。然后我们用len(list) 来计算list列表有多少个文件,这是为了帮助我们确定循环次数。

  接着我们通过一个for循环,把list中的文件添加到线程中数组threads[]中。接着启动threads[]线程组,最后打印结束时间。

split()可以将一个字符串拆分成两部分,然后取其中的一部分。

复制代码

>>> x = ‘testing.py’
>>> s = x.split(‘.’)[1]
>>> if s==’py’:
print s

py

复制代码

运行结果:

Start playing: 爱情买卖.mp3! Mon Apr 21 12:48:40 2014
Start playing: 阿凡达.mp4! Mon Apr 21 12:48:40 2014
Start playing: 爱情买卖.mp3! Mon Apr 21 12:48:42 2014
Start playing: 阿凡达.mp4! Mon Apr 21 12:48:45 2014
end:Mon Apr 21 12:48:50 2014

现在向list数组中添加一个文件,程序运行时会自动为其创建一个线程。

继续改进例子:

  通过上面的程序,我们发现player()用于判断文件扩展名,然后调用music()和move() ,其实,music()和move()完整工作是相同的,我们为什么不做一台超级播放器呢,不管什么文件都可以播放。经过改造,我的超级播放器诞生了。

super_player.py

复制代码

#coding=utf-8
from time import sleep, ctime
import threading

def super_player(file,time):
for i in range(2):
print ‘Start playing: %s! %s’ %(file,ctime())
sleep(time)

#播放的文件与播放时长
list = {‘爱情买卖.mp3′:3,’阿凡达.mp4′:5,’我和你.mp3’:4}

threads = []
files = range(len(list))

#创建线程
for file,time in list.items():
t = threading.Thread(target=super_player,args=(file,time))
threads.append(t)

if __name__ == ‘__main__’:
#启动线程
for i in files:
threads[i].start()
  for i in files:
   threads[i].join()

#主线程
print ‘end:%s’ %ctime()

复制代码

  首先创建字典list ,用于定义要播放的文件及时长(秒),通过字典的items()方法来循环的取file和time,取到的这两个值用于创建线程。

  接着创建super_player()函数,用于接收file和time,用于确定要播放的文件及时长。

  最后是线程启动运行。运行结果:
复制代码

Start playing: 爱情买卖.mp3! Fri Apr 25 09:45:09 2014
Start playing: 我和你.mp3! Fri Apr 25 09:45:09 2014
Start playing: 阿凡达.mp4! Fri Apr 25 09:45:09 2014
Start playing: 爱情买卖.mp3! Fri Apr 25 09:45:12 2014
Start playing: 我和你.mp3! Fri Apr 25 09:45:13 2014
Start playing: 阿凡达.mp4! Fri Apr 25 09:45:14 2014
end:Fri Apr 25 09:45:19 2014

复制代码

创建自己的多线程类

复制代码

#coding=utf-8
import threading
from time import sleep, ctime

class MyThread(threading.Thread):

def __init__(self,func,args,name=”):
threading.Thread.__init__(self)
self.name=name
self.func=func
self.args=args

def run(self):
apply(self.func,self.args)

def super_play(file,time):
for i in range(2):
print ‘Start playing: %s! %s’ %(file,ctime())
sleep(time)

list = {‘爱情买卖.mp3′:3,’阿凡达.mp4’:5}

#创建线程
threads = []
files = range(len(list))

for k,v in list.items():
t = MyThread(super_play,(k,v),super_play.__name__)
threads.append(t)

if __name__ == ‘__main__’:
#启动线程
for i in files:
threads[i].start()
  for i in files:
   threads[i].join()

#主线程
print ‘end:%s’ %ctime()

复制代码

MyThread(threading.Thread)

创建MyThread类,用于继承threading.Thread类。

__init__()

使用类的初始化方法对func、args、name等参数进行初始化。

apply()

  apply(func [, args [, kwargs ]]) 函数用于当函数参数已经存在于一个元组或字典中时,间接地调用函数。args是一个包含将要提供给函数的按位置传递的参数的元组。如果省略了args,任何参数都不会被传递,kwargs是一个包含关键字参数的字典。

apply() 用法:

复制代码

#不带参数的方法
>>> def say():
print ‘say in’

>>> apply(say)
say in

#函数只带元组的参数
>>> def say(a,b):
print a,b

>>> apply(say,(‘hello’,’虫师’))
hello 虫师

#函数带关键字参数
>>> def say(a=1,b=2):
print a,b

>>> def haha(**kw):
apply(say,(),kw)

>>> haha(a=’a’,b=’b’)
a b

复制代码

MyThread(super_play,(k,v),super_play.__name__)

由于MyThread类继承threading.Thread类,所以,我们可以使用MyThread类来创建线程。

运行结果:

Start playing: 爱情买卖.mp3! Fri Apr 25 10:36:19 2014
Start playing: 阿凡达.mp4! Fri Apr 25 10:36:19 2014
Start playing: 爱情买卖.mp3! Fri Apr 25 10:36:22 2014
Start playing: 阿凡达.mp4! Fri Apr 25 10:36:24 2014
all end: Fri Apr 25 10:36:29 2014

python 多线程理解(一)

单线程

  在好些年前的MS-DOS时代,操作系统处理问题都是单任务的,我想做听音乐和看电影两件事儿,那么一定要先排一下顺序。

(好吧!我们不纠结在DOS时代是否有听音乐和看影的应用。^_^)
复制代码

from time import ctime,sleep

def music():
for i in range(2):
print “I was listening to music. %s” %ctime()
sleep(1)

def move():
for i in range(2):
print “I was at the movies! %s” %ctime()
sleep(5)

if __name__ == ‘__main__’:
music()
move()
print “all over %s” %ctime()

复制代码

  我们先听了一首音乐,通过for循环来控制音乐的播放了两次,每首音乐播放需要1秒钟,sleep()来控制音乐播放的时长。接着我们又看了一场电影,

每一场电影需要5秒钟,因为太好看了,所以我也通过for循环看两遍。在整个休闲娱乐活动结束后,我通过

print “all over %s” %ctime()

看了一下当前时间,差不多该睡觉了。

运行结果:
复制代码

>>=========================== RESTART ================================
>>>
I was listening to music. Thu Apr 17 10:47:08 2014
I was listening to music. Thu Apr 17 10:47:09 2014
I was at the movies! Thu Apr 17 10:47:10 2014
I was at the movies! Thu Apr 17 10:47:15 2014
all over Thu Apr 17 10:47:20 2014

复制代码

  

  其实,music()和move()更应该被看作是音乐和视频播放器,至于要播放什么歌曲和视频应该由我们使用时决定。所以,我们对上面代码做了改造:
复制代码

#coding=utf-8
import threading
from time import ctime,sleep

def music(func):
for i in range(2):
print “I was listening to %s. %s” %(func,ctime())
sleep(1)

def move(func):
for i in range(2):
print “I was at the %s! %s” %(func,ctime())
sleep(5)

if __name__ == ‘__main__’:
music(u’爱情买卖’)
move(u’阿凡达’)

print “all over %s” %ctime()

复制代码

  对music()和move()进行了传参处理。体验中国经典歌曲和欧美大片文化。

运行结果:
复制代码

>>> ======================== RESTART ================================
>>>
I was listening to 爱情买卖. Thu Apr 17 11:48:59 2014
I was listening to 爱情买卖. Thu Apr 17 11:49:00 2014
I was at the 阿凡达! Thu Apr 17 11:49:01 2014
I was at the 阿凡达! Thu Apr 17 11:49:06 2014
all over Thu Apr 17 11:49:11 2014

复制代码

多线程

  科技在发展,时代在进步,我们的CPU也越来越快,CPU抱怨,P大点事儿占了我一定的时间,其实我同时干多个活都没问题的;于是,操作系统就进入了多任务时代。我们听着音乐吃着火锅的不在是梦想。

  python提供了两个模块来实现多线程thread 和threading ,thread 有一些缺点,在threading 得到了弥补,为了不浪费你和时间,所以我们直接学习threading 就可以了。

继续对上面的例子进行改造,引入threadring来同时播放音乐和视频:
复制代码

#coding=utf-8
import threading
from time import ctime,sleep

def music(func):
for i in range(2):
print “I was listening to %s. %s” %(func,ctime())
sleep(1)

def move(func):
for i in range(2):
print “I was at the %s! %s” %(func,ctime())
sleep(5)

threads = []
t1 = threading.Thread(target=music,args=(u’爱情买卖’,))
threads.append(t1)
t2 = threading.Thread(target=move,args=(u’阿凡达’,))
threads.append(t2)

if __name__ == ‘__main__’:
for t in threads:
t.setDaemon(True)
t.start()

print “all over %s” %ctime()

复制代码

import threading

首先导入threading 模块,这是使用多线程的前提。

threads = []

t1 = threading.Thread(target=music,args=(u’爱情买卖’,))

threads.append(t1)

  创建了threads数组,创建线程t1,使用threading.Thread()方法,在这个方法中调用music方法target=music,args方法对music进行传参。 把创建好的线程t1装到threads数组中。

  接着以同样的方式创建线程t2,并把t2也装到threads数组。

for t in threads:

  t.setDaemon(True)

  t.start()

最后通过for循环遍历数组。(数组被装载了t1和t2两个线程)

setDaemon()

  setDaemon(True)将线程声明为守护线程,必须在start() 方法调用之前设置,如果不设置为守护线程程序会被无限挂起。子线程启动后,父线程也继续执行下去,当父线程执行完最后一条语句print “all over %s” %ctime()后,没有等待子线程,直接就退出了,同时子线程也一同结束。

start()

开始线程活动。

运行结果:

>>> ========================= RESTART ================================
>>>
I was listening to 爱情买卖. Thu Apr 17 12:51:45 2014 I was at the 阿凡达! Thu Apr 17 12:51:45 2014 all over Thu Apr 17 12:51:45 2014

  从执行结果来看,子线程(muisc 、move )和主线程(print “all over %s” %ctime())都是同一时间启动,但由于主线程执行完结束,所以导致子线程也终止。

继续调整程序:
复制代码


if __name__ == ‘__main__’:
for t in threads:
t.setDaemon(True)
t.start()

t.join()

print “all over %s” %ctime()

复制代码

  我们只对上面的程序加了个join()方法,用于等待线程终止。join()的作用是,在子线程完成运行之前,这个子线程的父线程将一直被阻塞。

  注意: join()方法的位置是在for循环外的,也就是说必须等待for循环里的两个进程都结束后,才去执行主进程。

运行结果:
复制代码

>>> ========================= RESTART ================================
>>>
I was listening to 爱情买卖. Thu Apr 17 13:04:11 2014 I was at the 阿凡达! Thu Apr 17 13:04:11 2014

I was listening to 爱情买卖. Thu Apr 17 13:04:12 2014
I was at the 阿凡达! Thu Apr 17 13:04:16 2014
all over Thu Apr 17 13:04:21 2014

复制代码

  从执行结果可看到,music 和move 是同时启动的。

  开始时间4分11秒,直到调用主进程为4分22秒,总耗时为10秒。从单线程时减少了2秒,我们可以把music的sleep()的时间调整为4秒。
复制代码


def music(func):
for i in range(2):
print “I was listening to %s. %s” %(func,ctime())
sleep(4)

复制代码

执行结果:
复制代码

>>> ====================== RESTART ================================
>>>
I was listening to 爱情买卖. Thu Apr 17 13:11:27 2014I was at the 阿凡达! Thu Apr 17 13:11:27 2014

I was listening to 爱情买卖. Thu Apr 17 13:11:31 2014
I was at the 阿凡达! Thu Apr 17 13:11:32 2014
all over Thu Apr 17 13:11:37 2014

复制代码

  子线程启动11分27秒,主线程运行11分37秒。

  虽然music每首歌曲从1秒延长到了4 ,但通多程线的方式运行脚本,总的时间没变化。

本文从感性上让你快速理解python多线程的使用,更详细的使用请参考其它文档或资料。

Python 调用jenkins接口自动发布脚本

#!/opt/venv/bin/python
# coding: utf-8
# author: —

import jenkins
import sys
import threading
import time

url_dict={‘qa’:’http://xx.xx.xx.xx/jenkins/’,
‘jq’:’http://xx.xx.xx.xx/jenkins’,
‘prd’:’http://xx.xx.xx.xx/jenkins’}

username_dict={‘qa’:’admin’,’jq’:’admin’,’prd’:’admin’}

token_dict={‘qa’:’009753b3087cdc9be9c76c9a40f0f99a’,
‘jq’:’5a2ded6a567f5b76d23e8e0c241fc478′,
‘prd’:’5a2ded6a567f5b76d23e8e0c241fc478′}
str = “-”
env2job_dict={‘qa-routine’:”checkout_rls”,’qa-emergy’:”checkout_full”,’prd’:”checkout_zxq_rls2rls”}
help=”’For example:
#不切分支
./jenkins_deploy.py rebuild qa-job1,qa-job2,qa-job3
#qa例行发布
./jenkins_deploy.py qa-routine qa-job1,qa-job2,qa-job3,qa-job4
#qa紧急发布
./jenkins_deploy.py qa-emergy qa-job1,qa-job2,qa-job3
#生产、预生产发布
./jenkins_deploy.py prd prd-job1,prd-job2,prd-job3,prd-job4”’

class myjenkins:
def __init__(self,env):
self.server = jenkins.Jenkins(url_dict[env],
username=username_dict[env],
password=token_dict[env])

def build_job(self,job_name,param_dict=”):
next_build_num=self.server.get_job_info(job_name)[‘nextBuildNumber’]
if param_dict==”:
self.server.build_job(job_name)
else:
self.server.build_job(job_name,parameters=param_dict)
while 1:
try:
result=self.server.get_build_info(job_name,next_build_num)
if (result[‘result’] != None) and (result[‘building’] == False):
break
except:
continue
print(“%s(%s) build %s” %(job_name,result[‘displayName’],result[‘result’]))
return result[‘result’]

@staticmethod
def usage():
print(help)

def func(job_name,branch_do):
job_name=job_name.strip()
env = job_name.split(‘-‘)[0]
project_name = job_name.split(‘-‘)[1:]
project_name = str.join(project_name)
#newrvm特殊
project_name = project_name.strip(‘-BJ’)
project_name = project_name.strip(‘-SH’)
project_name = project_name.strip(‘-GB’)
project_name = project_name.strip(‘-SH-Auding’)
project_name = project_name.strip(‘-SH-Old’)

a = myjenkins(env)
if branch_do != ‘rebuild’:
checkout_result = a.build_job(env2job_dict[branch_do], {‘ProjectName’: project_name})
else:
checkout_result = ‘SUCCESS’

if checkout_result == ‘SUCCESS’:
a.build_job(job_name)
else:
print(“Checkout % failed” % (job_name))

def main():
branch_do=sys.argv[1]

if branch_do == ‘–help’:
myjenkins.usage()
exit()

jobs = sys.argv[2]
jobs=jobs.split(‘,’)

if jobs != []:
for job_name in jobs:
S=threading.Thread(target=func,args=(job_name,branch_do))
S.start()
time.sleep(15) #不sleep的话,切分支会抓到同一个next_build_num

if __name__ == ‘__main__’:
main()

自动推送微信公共账号群消息

微信公众平台的基础接口没有推送群消息的api(高级接口貌似也没有),所以用python写了个程序模拟微信公众平台的post请求来实现这个功能,附带实现了发送消息给单个用户。

需要的模块

requests:给人用的HTTP请求模块,python自带的urllib和urllib2实在是难用。requests支持会话模式,意思就是所有的请求都用一个实例,共享同一个cookies,十分方便。具体参考官方文档(中文):http://cn.python-requests.org/zh_CN/latest/index.html

实现

requests.seesion()就是会话模式

首先设置主请求头

然后login()函数登录,获取token。发送的密码用md5加密

最后masssend()函数发送群消息

singlesend()函数用来给单人发送消息,形参tofakeid不是用户的微信号,并且和api中”FromUserName 发送方帐号”也不一样,这样就没办法将用户直接关联起来,只能手动观察绑定-。-

ps:如果在本机浏览器上登录了微信公众平台,然后在另一个电脑上运行脚本,可能会导致脚本登录失败,解决的办法是退出本机登录。

自动化日常巡检程序-Python实现

程序说明

程序是通过使用python实现ssh连接到服务器去实行shell脚本,返回数据,并且生成excel,对不正常指标进行标红处理。

Python要实现ssh连接服务器还需要其他模块。

python-2.7.5、paramiko-1.15.2、xlwt-1.0.0

程序流程

daily check

由于这边是在windows下面执行的,下面我将改成在linux系统下面执行

程序结构与程序说明

此程序由主程序、主机信息文件、命令文件三个文件组成。

` daily_check.py <此文件是python的主程序
` 日期ycq.txt <生成的巡检信息文件
` check.log <日志文件
| hosts <存放着要执行那些命令和检查那些主机的文件
|     ` host.info <存放着需要巡检的主机IP和用户名密码
|     `xx.xx.xx.xx.sh<–脚本文件生成下面的文件
|     ` xx.xx.xx.xx.cmd <存放相关主机需要执行的命令
执行了daily_check.py生成了xunjian.txt文件,我们需要将文件的内容都拷贝到一个excel文件中查看。

并标出相关的预警的信息。如下:

daily check上图显示了5列:

  • 项目:检查了什么。
  • 命令:检查使用的命令。
  • 基线:比对的标准。
  • 巡检情况:检查出来的信息。
  • 主机:是在那台主机上检查的。
    • *.cmd文件

    此文件存放了巡检需要执行的命令、校验标准和命令是检查什么项目的,如下图:

    daily check
  • 以上文件的每一行分为3个部分:
    • 校验项目。
    • 校验shell命令。
    • 校验标准:校验标准指定一个操作符号(>、<、=),具体用途例如:<85% 如果检查出来的结果 >85%就超标了。

    以上三个部分是使用 “#” 分割(注意:请在命令中避免使用 “#”,以免出现程序分割错误)。

    host.info文件

    此文件中存放了需要巡检的主机的IP和用户名信息,它们使用空格分开,如果有不需要巡检的主机可以在里面使用 (#) 在注释,和shell脚本的注释是一样的。如图:

    daily check

如果有行的服务器需要巡检,需要添加相关 主机 信息到此文件中。

执行巡检先要执行xx.xx.xx.xx.sh脚本,生产xx.xx.xx.xx.cmd,然后再执行主程序daliy_check.py,现将脚本贴到下面:
1.主程序:daliy_check.py
#!/bin/env python
# -*- coding:utf-8 -*-

# program: ssh remote execute command
#          genaral data story local
# author: ycq
# date: 2017-08-18

import sys
import paramiko
import xlwt
import time
from datetime import datetime
import logging

logging.basicConfig(level=logging.INFO,
format=’%(asctime)s %(name)-12s %(levelname)-8s %(message)s’,
filename=’check.log’,
filemode=’w’,
datefmt=’%Y-%m-%d %X’)
reload(sys)
ISOTIMEFORMAT=’%Y-%m-%d’
sys.setdefaultencoding(‘utf-8’)

def header(work_sheet):
work_sheet.write(0, 0, ‘检查项目’, style0)
work_sheet.write(0, 1, ‘命令’, style0)
work_sheet.write(0, 2, ‘基准’, style0)
work_sheet.write(0, 3, ‘检查结果’, style0)
work_sheet.write(0, 4, ‘检查主机’, style0)
logging.info(‘header add ok!’)
return work_sheet

def run_host_sh(host, port, username, password, work_sheet):
ssh = paramiko.SSHClient()
command_file = open(‘./hosts/’ + host + ‘.cmd’, ‘r’)

for line in command_file.readlines():
rows = len(work_sheet.rows)
line = line.strip(‘\n’)
items = line.split(‘:’)

check_type = items[0].decode(‘utf-8’).encode(‘utf-8’)
check_cmd = items[1].decode(‘gbk’).encode(‘utf-8’)
check_base_line = items[2].decode(‘gbk’).encode(‘utf-8’)
opration = check_base_line[0:1]
base_value = check_base_line[1:]

work_sheet.write(rows, 0, check_type)
work_sheet.write(rows, 1, check_cmd)
work_sheet.write(rows, 2, check_base_line)

ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
ssh.connect(host, port, username, password, key_filename=None, timeout=2)
except Exception, e:
logging.error(‘can not connect host: ‘ + host)
logging.error(‘command can not exec: ‘ + check_cmd)
logging.error(e)
continue
stdin, stdout, stderr = ssh.exec_command(check_cmd)
return_info = stdout.read().strip()

style_red = xlwt.easyxf(‘pattern: pattern solid, fore_colour red;’ +
‘font: name Times New Roman, color-index black, bold on;’ +
‘borders: left thick, right thick, top thick, bottom thick;’ +
‘align: horiz center’)

error_flag = False

if opration == ‘>’:
if str(base_value) > str(return_info):
error_flag = True
elif opration == ‘=’:
if str(return_info) != str(base_value):
error_flag = True
elif opration == ‘<‘:
if str(base_value) < str(return_info):
error_flag = True

if error_flag:
logging.warning(host + ‘ checked diff’)
logging.warning(‘command is: ‘ + check_cmd)
work_sheet.write(rows, 3, return_info.decode(‘gbk’).encode(‘utf-8’), style_red)
else:
work_sheet.write(rows, 3, return_info.decode(‘gbk’).encode(‘utf-8’))

work_sheet.write(rows, 4, ‘server IP: ‘ + host)

check_info = check_type + ‘\t’ + check_cmd + ‘\t’ + check_base_line + ‘\t’ + return_info
check_info = check_info.strip(‘\n’)
check_info += ‘\t’ + ‘server IP: ‘ + host
logging.info(check_info)
command_file.close()
ssh.close()
return work_sheet

if __name__==’__main__’:
#   try:
#     main()
#   except Exception,e:
#     print e

style0 = xlwt.easyxf(‘pattern: pattern solid, fore_colour yellow;’ +
‘font: name Times New Roman, color-index black, bold on;’ +
‘borders: left thick, right thick, top thick, bottom thick;’ +
‘align: horiz center’,
num_format_str=’0,000.00′)

work_book = xlwt.Workbook(encoding=’utf-8′)
work_sheet = work_book.add_sheet(‘A Test Sheet’)
work_sheet = header(work_sheet)

hosts_file = open(‘./hosts/host.info’, ‘r’)

for line in hosts_file.readlines():
if line[0:1] == ‘#’: continue
line = line.strip(‘\n’)
items = line.split()
port = 22
host = items[0]
username = items[1]
password = items[2]

work_sheet = run_host_sh(host, port, username, password, work_sheet)
logging.info(host + ‘ check finish !\n’)
file_pre = time.strftime(ISOTIMEFORMAT, time.localtime(time.time()))
work_book.save(file_pre + ‘_ycq.xls’)

2.host.info
xx.xx.xx.xx user passwd
3.xx.xx.xx.xx.sh
#!/bin/bash
disk=$(df -lP | grep -e ‘/alidata1$’ | awk ‘{print $5}’)
echo -e 磁盘空间:$disk:”<85%” > 10.14.12.20.cmd
used_free=$(free -m | sed -n ‘2p’ | awk ‘{printf(“%d%\n”,$3/$2*100)}’)
echo -e 已使用内存:$used_free:”<10%” >> 10.14.12.20.cmd

Python将文本生成二维码

#coding:utf-8
'''
Python生成二维码 v1.0
主要将文本生成二维码图片
 
测试一:将文本生成白底黑字的二维码图片
测试二:将文本生成带logo的二维码图片
 
'''
 
__author__ = 'Xue'
 
import qrcode
from PIL import Image
import os
 
#生成二维码图片
def make_qr(str,save):
    qr=qrcode.QRCode(
        version=4,  #生成二维码尺寸的大小 1-40  1:21*21(21+(n-1)*4)
        error_correction=qrcode.constants.ERROR_CORRECT_M, #L:7% M:15% Q:25% H:30%
        box_size=10, #每个格子的像素大小
        border=2, #边框的格子宽度大小
    )
    qr.add_data(str)
    qr.make(fit=True)
 
    img=qr.make_image()
    img.save(save)
 
 
#生成带logo的二维码图片
def make_logo_qr(str,logo,save):
    #参数配置
    qr=qrcode.QRCode(
        version=4,
        error_correction=qrcode.constants.ERROR_CORRECT_Q,
        box_size=8,
        border=2
    )
    #添加转换内容
    qr.add_data(str)
    #
    qr.make(fit=True)
    #生成二维码
    img=qr.make_image()
    #
    img=img.convert("RGBA")
 
    #添加logo
    if logo and os.path.exists(logo):
        icon=Image.open(logo)
        #获取二维码图片的大小
        img_w,img_h=img.size
 
        factor=4
        size_w=int(img_w/factor)
        size_h=int(img_h/factor)
 
        #logo图片的大小不能超过二维码图片的1/4
        icon_w,icon_h=icon.size
        if icon_w>size_w:
            icon_w=size_w
        if icon_h>size_h:
            icon_h=size_h
        icon=icon.resize((icon_w,icon_h),Image.ANTIALIAS)
        #详见:http://pillow.readthedocs.org/handbook/tutorial.html
 
        #计算logo在二维码图中的位置
        w=int((img_w-icon_w)/2)
        h=int((img_h-icon_h)/2)
        icon=icon.convert("RGBA")
        img.paste(icon,(w,h),icon)
        #详见:http://pillow.readthedocs.org/reference/Image.html#PIL.Image.Image.paste
 
    #保存处理后图片
    img.save(save)
 
 
if __name__=='__main__':
    save_path='theqrcode.png' #生成后的保存文件
    logo='logo.jpg'  #logo图片
 
    str=raw_input('请输入要生成二维码的文本内容:')
 
    #make_qr(str)
 
    make_logo_qr(str,logo,save_path)

Python中第三方的用于解析HTML的库:BeautifulSoup

1.背景

在Python去写爬虫,网页解析等过程中,常常需要涉及到HTML等网页的解析。

当然,对于简单的HTML中内容的提取,Python内置的正则表达式Re模块,就足够用了,

但是对于复杂的HTML的处理,尤其是一些非法的,有bug的html代码的处理,那么最好还是用专门的HTML的解析的库。

Python中的,专门用于HTML解析的库,比较好用的,就是BeautifulSoup。

2.BeautifulSoup简介

Python中,专门用于HTML/XML解析的库;

特点是:

即使是有bug,有问题的html代码,也可以解析。

功能很强大;

BeautifulSoup的主页是:

http://www.crummy.com/software/BeautifulSoup/

 

2.1.BeautifulSoup的版本

BeautifulSoup主要有两个版本:

 

2.1.1.BeautifulSoup 3

之前的,比较早的,是3.x的版本。

2.1.1.1BeautifulSoup 3的在线文档

最新的,可用的,在线文档是:

http://www.crummy.com/software/BeautifulSoup/bs3/documentation.html

中文版的是:

http://www.crummy.com/software/BeautifulSoup/bs3/documentation.zh.html

 

2.1.1.2下载BeautifulSoup 3

http://www.crummy.com/software/BeautifulSoup/bs3/download//3.x/

中可以下载到很多版本,比如我常用的3.0.6的版本:

BeautifulSoup-3.0.6.py

http://www.crummy.com/software/BeautifulSoup/bs3/download//3.x/BeautifulSoup-3.0.6.py

 

2.1.2.BeautifulSoup 4:缩写为bs4

最新的v4版本的BeautifulSoup,改名为bs4了。

 

注意:

使用bs4时,导入BeautifulSoup的写法是:

1
from bs4 import BeautifulSoup;

然后就可以像之前3.x中一样,直接使用BeautifulSoup了。

bs4的在线文档

http://www.crummy.com/software/BeautifulSoup/bs4/doc/

 

下载bs4

http://www.crummy.com/software/BeautifulSoup/bs4/download/

可以下载到对应的bs4的版本,比如:

此时最新的版本是:

beautifulsoup4-4.1.3.tar.gz

http://www.crummy.com/software/BeautifulSoup/bs4/download/beautifulsoup4-4.1.3.tar.gz

3.BeautifulSoup的用法

如何安装BeautifulSoup,3.0.6之前,都是不需要安装的,所以使用起来最简单,直接下载对应的版本,比如:

http://www.crummy.com/software/BeautifulSoup/bs3/download//3.x/BeautifulSoup-3.0.6.py

得到了BeautifulSoup-3.0.6.py,然后改名为:BeautifulSoup.py

然后,放到和你当前的python文件同目录下,比如我当前python文件是:

D:\tmp\tmp_dev_root\python\beautifulsoup_demo\beautifulsoup_demo.py

那就放到

D:\tmp\tmp_dev_root\python\beautifulsoup_demo\

下面,和beautifulsoup_demo.py同目录。

3.0.6之后:需要安装BeautifulSoup后才可使用

使用BeautifulSoup提取html中的某个内容

关于最简单的,最基本的用法,提取html中的某个内容,具体用法,就是使用对应的find函数。

完整代码是:

 

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
41
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Function:
【教程】Python中第三方的用于解析HTML的库:BeautifulSoup
Author:     Crifan Li
Version:    2012-12-26
Contact:    admin at crifan dot com
"""
from BeautifulSoup import BeautifulSoup;
def beautifulsoupDemo():
    demoHtml = """
<html>
<body>
<div class="icon_col">
        <h1 class="h1user">crifan</h1>
 </div>
 </body>
</html>
""";
    soup = BeautifulSoup(demoHtml);
    print "type(soup)=",type(soup); #type(soup)= <type 'instance'>
    print "soup=",soup;
    
    # 1. extract content
    # method 1: no designate para name
    #h1userSoup = soup.find("h1", {"class":"h1user"});
    # method 2: use para name
    h1userSoup = soup.find(name="h1", attrs={"class":"h1user"});
    # more can found at:
    #http://www.crummy.com/software/BeautifulSoup/bs3/documentation.zh.html#find%28name,%20attrs,%20recursive,%20text,%20**kwargs%29
    print "h1userSoup=",h1userSoup; #h1userSoup= <h1 class="h1user">crifan</h1>
    h1userUnicodeStr = h1userSoup.string;
    print "h1userUnicodeStr=",h1userUnicodeStr; #h1userUnicodeStr= crifan
if __name__ == "__main__":
    beautifulsoupDemo();

输出为:

1
2
3
4
5
6
7
8
9
10
11
12
13
D:\tmp\tmp_dev_root\python\beautifulsoup_demo>beautifulsoup_demo.py
type(soup)= <type 'instance'>
soup=
<html>
<body>
<div class="icon_col">
<h1 class="h1user">crifan</h1>
</div>
</body>
</html>
h1userSoup= <h1 class="h1user">crifan</h1>
h1userUnicodeStr= crifan

使用BeautifulSoup修改/改变/替换原先html中的某个内容

如果需要改变原先html中的某个值,可以参考官网解释:

修改属性值

后来证实,只能改(Tag的)中的属性的值,不能改(Tag的)的值本身

完整示例代码为:

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
41
42
43
44
45
46
47
48
49
50
51
52
53
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Function:
【教程】Python中第三方的用于解析HTML的库:BeautifulSoup
Author:     Crifan Li
Version:    2013-02-01
Contact:    admin at crifan dot com
"""
from BeautifulSoup import BeautifulSoup;
def beautifulsoupDemo():
    demoHtml = """
<html>
<body>
<div class="icon_col">
        <h1 class="h1user">crifan</h1>
 </div>
 </body>
</html>
""";
    soup = BeautifulSoup(demoHtml);
    print "type(soup)=",type(soup); #type(soup)= <type 'instance'>
    print "soup=",soup;
    
    print '{0:=^80}'.format(" 1. extract content ");
    # method 1: no designate para name
    #h1userSoup = soup.find("h1", {"class":"h1user"});
    # method 2: use para name
    h1userSoup = soup.find(name="h1", attrs={"class":"h1user"});
    # more can found at:
    #http://www.crummy.com/software/BeautifulSoup/bs3/documentation.zh.html#find%28name,%20attrs,%20recursive,%20text,%20**kwargs%29
    print "h1userSoup=",h1userSoup; #h1userSoup= <h1 class="h1user">crifan</h1>
    h1userUnicodeStr = h1userSoup.string;
    print "h1userUnicodeStr=",h1userUnicodeStr; #h1userUnicodeStr= crifan
    
    print '{0:=^80}'.format(" 2. demo change tag value and property ");
    print '{0:-^80}'.format(" 2.1 can NOT change tag value ");
    print "old tag value=",soup.body.div.h1.string; #old tag value= crifan
    changedToString = u"CrifanLi";
    soup.body.div.h1.string = changedToString;
    print "changed tag value=",soup.body.div.h1.string; #changed tag value= CrifanLi
    print "After changed tag value, new h1=",soup.body.div.h1; #After changed tag value, new h1= <h1 class="h1user">crifan</h1>
    print '{0:-^80}'.format(" 2.2 can change tag property ");  
    soup.body.div.h1['class'] = "newH1User";
    print "changed tag property value=",soup.body.div.h1; #changed tag property value= <h1 class="newH1User">crifan</h1>
if __name__ == "__main__":
    beautifulsoupDemo();

更多的,用法和使用心得,部分内容,请参考:

http://www.crifan.com/files/doc/docbook/python_topic_beautifulsoup/release/html/python_topic_beautifulsoup.html

使用virtualenv搭建独立的Python环境

virtualenv可以搭建虚拟且独立的python环境,可以使每个项目环境与其他项目独立开来,保持环境的干净,解决包冲突问题。

一、安装virtualenv

virtualenv实际上是一个python包,所以我们可以用easy_install或者pip安装。下面介绍在CentOS系统上的安装方法。

easy_install安装方式:

1
2
[root@localhost ~]# yum install python-setuptools python-devel
[root@localhost ~]# easy_install virtualenv

pip安装方式:

1
2
[root@localhost ~]# easy_install pip
[root@localhost ~]# pip install virtualenv

yum安装方式(epel源):

1
[root@localhost ~]# yum install python-virtualenv

二、创建python虚拟环境

使用virtualenv命令创建python虚拟环境:virtualenv [虚拟环境名称]。

1
2
3
[root@localhost ~]# virtualenv env1
New python executable in env1/bin/python
Installing setuptools, pip...done.

执行后,在本地会生成一个与虚拟环境同名的文件夹。

如果你的系统里安装有不同版本的python,可以使用–python参数指定虚拟环境的python版本:

1
2
3
4
5
[root@localhost ~]# virtualenv --python=/usr/local/python-2.7.8/bin/python2.7 env1
Running virtualenv with interpreter /usr/local/python-2.7.8/bin/python2.7
New python executable in env1/bin/python2.7
Also creating executable in env1/bin/python
Installing setuptools, pip...done.

实测默认情况下虚拟环境不会依赖系统环境的global site-packages。比如系统环境里安装了MySQLdb模块,在虚拟环境里import MySQLdb会提示ImportError。如果想依赖系统环境的第三方软件包,可以使用参数–system-site-packages。此外,也可使用virtualenvwrapper的toggleglobalsitepackages命令控制当前环境是否使用global site-packages。

1
2
3
[root@localhost ~]# virtualenv --system-site-packages env1
New python executable in env1/bin/python
Installing setuptools, pip...done.

三、启动虚拟环境

进入虚拟环境目录,启动虚拟环境,如下:

1
2
3
4
[root@localhost ~]# cd env1/
[root@localhost env1]# source bin/activate
(env1)[root@localhost env1]# python -V
Python 2.7.8

此时命令行前面会多出一个括号,括号里为虚拟环境的名称。以后easy_install或者pip安装的所有模块都会安装到该虚拟环境目录里。

四、退出虚拟环境

退出虚拟环境:deactivate

1
2
(env1)[root@localhost env1]# deactivate
[root@localhost env1]#

五、使用virtualenvwrapper

virtualenvwrapper是virtualenv的扩展工具,可以方便的创建、删除、复制、切换不同的虚拟环境。

1.安装virtualenvwrapper

1
[root@localhost ~]# easy_install virtualenvwrapper

或者:

1
[root@localhost ~]# pip install virtualenvwrapper

创建一个文件夹,用于存放所有的虚拟环境:

1
[root@localhost ~]# mkdir ~/workspaces

设置环境变量,把下面两行添加到~/.bashrc里。

1
2
[root@localhost ~]# export WORKON_HOME=~/workspaces
[root@localhost ~]# source /usr/bin/virtualenvwrapper.sh

然后就可以使用virtualenvwrapper了。
2.创建虚拟环境:mkvirtualenv [虚拟环境名称]

1
2
3
4
5
6
7
[root@localhost ~]# mkvirtualenv env1
New python executable in env1/bin/python
Installing setuptools, pip...done.
(env1)[root@localhost ~]# mkvirtualenv env2
New python executable in env2/bin/python
Installing setuptools, pip...done.
(env2)[root@localhost ~]#

注意:mkvirtualenv可以使用virtualenv的参数,比如–python来指定python版本。创建虚拟环境后,会自动切换到此虚拟环境里。虚拟环境目录都在WORKON_HOME里。

3.列出虚拟环境:lsvirtualenv -b

1
2
3
(env2)[root@localhost ~]# lsvirtualenv -b
env1
env2

4.切换虚拟环境:workon [虚拟环境名称]

1
2
3
(env2)[root@localhost ~]# workon env1
(env1)[root@localhost ~]# echo $VIRTUAL_ENV
/root/workspaces/env1

5.查看环境里安装了哪些包:lssitepackages

6.进入当前环境的目录:cdvirtualenv [子目录名]

1
2
3
4
5
6
(env1)[root@localhost ~]# cdvirtualenv
(env1)[root@localhost env1]# pwd
/root/workspaces/env1
(env1)[root@localhost env1]# cdvirtualenv bin
(env1)[root@localhost bin]# pwd
/root/workspaces/env1/bin

进入当前环境的site-packages目录:cdsitepackages [子目录名]

1
2
3
4
5
6
(env1)[root@localhost env1]# cdsitepackages
(env1)[root@localhost site-packages]# pwd
/root/workspaces/env1/lib/python2.6/site-packages
(env1)[root@localhost site-packages]# cdsitepackages pip
(env1)[root@localhost pip]# pwd
/root/workspaces/env1/lib/python2.6/site-packages/pip

7.控制环境是否使用global site-packages:toggleglobalsitepackages

8.复制虚拟环境:cpvirtualenv [source] [dest]

1
2
3
[root@localhost ~]# cpvirtualenv env1 env3
Copying env1 as env3...
(env3)[root@localhost ~]#

9.退出虚拟环境:deactivate

10.删除虚拟环境:rmvirtualenv [虚拟环境名称]

1
2
[root@localhost ~]# rmvirtualenv env2
Removing env2...

【爬虫其实很简单】requests 与 beautiful soup基础入门

本篇教程内容完全针对初学者,如果你需要更进阶一点的知识,本篇可能给你的帮助十分有限

准备工作

首先确认代码环境,我们使用python来进行爬虫的开发。在这里我使用的版本是python3.5。这个教程应该适用于所有python3.x版本, python2.x可能做少许的改动就可以直接运行。

这次教程中我们要用到的模块是requests模块。如果没有安装requests模块的同学需要先安装一下。

Mac端的同学先打开terminal,Windows端的同学可以按下’win + r’后调出”运行”,然后输入cmd打开命令指示行。

【windows】按下win + r

【windows】打开cmd

【Mac】打开terminal

打开之后输入

pip install requests

来安装这个模块。

requests是一个非常方便的模块。可能你会看到有很多的代码在使用urllib的模块,这个教程没有使用这些模块的原因是因为它们相比较requests而言都比较复杂,作为一个初学者,我们没有必要一味的攀高。往往比工具更重要的是思想,我们学习的主要是思想,而非工具本身。真正的收获,一定是你忘记你的所有所学之后剩下的东西。

好了我们不说废话了,开始进入正题吧!

小试身手

我们先从简单的地方开始——爬取百度的首页。

打开python,输入下面的代码

import requests

r = requests.get('https://baidu.com')
print(r.text)

你看,百度的界面我们就已经拿到了

百度的网页文件

可能有的同学一开始这样爬会得到一个timeout的错误。如果出现了这样的情况,就是网站怀疑是一个机器人在访问自己,所以作出了一定的阻止。

那怎么办呢?没有关系,我们稍微修改一下我们的代码,改成

headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'}
r = requests.get('https://baidu.com', headers = headers)

headers的意思就是告诉网站,我们是一个正常的浏览器在给它发送信息,请它给我们正确的信息。

同样的,很多网站不需要登录,直接就可以访问其中的内容,比如糗事百科煎蛋网等等都可以直接爬取下来。

怎么样,是不是有一丝小小的成就感?

你可能就会问了,网页文件里面有那么多的无用信息,我们要怎么把它提取出来呢?

这个时候我们通常有两种做法,一种是正则表达式,一种是通过网页的结构对内容提取。

因为正则表达式相较后者更为复杂,对新手并不十分友好。所以我们这次的爬虫使用直接对网页的内容进行提取的方法来获取信息。如果你很想了解正则表达式的使用方式,你可以期待本教程的后续更新或者崔庆才老师博客中的正则表达式教程

Beautiful soup 的安装

Beautiful soup是另一个python的模块,我们将用这个模块来分解网页的结构,并对其中的内容进行提取。

同样的,Beautiful soup是一个第三方模块,我们需要使用

pip install beautifulsoup4

来对模块进行安装。

但是这还不够,Beautiful soup需要lxml包对文件进行处理,所以在安装完bs4之后你还需要安装lxml包:

pip install lxml

踏上正轨

在进一步讲Beautiful soup的使用之前,我们先分析一下要爬取的网页的结构,这样我们才能更加有效的针对网页的结构对网页的内容进行提取。

这里我们以糗事百科为例进行讲解。

第一件事仍然是我们先把它的页面爬取下来,也就是

import requests


headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'}
r = requests.get('http://www.qiushibaike.com', headers = headers)
content = r.text
print(content)

可以看到网页的内容已经被我们储存到了content变量中,打印出来是这样的:

content

接下来我们来分析网站的结构。

可能你也发现了,直接使用我们打印出来的结果分析起来十分吃力。所以我们使用更加高效的工具——开发者工具(Developer tools)来进行分析。

通常来说任何一个浏览器都有开发者工具,这里我们以Chrome为例。

Mac端在打开糗事百科之后按下option+command+I, Windows端直接按下F12即可。

Developer tools

可以看到,只要我们鼠标移到对应的标签(HTML tag)上,chrome就会帮我们把标签里面包含的网页内容高亮出来。

我们要的内容很简单,就是里面的段子。所以我们右键点击段子所在的位置,点击审查元素(Inspect),chrome就会自动找到该内容对应的标签。

审查元素

可以看到我们要的段子的内容就储存在这个叫做span的标签中。

段子内容

我们再往上追寻,可以看到<span>标签是属于一个叫做<div class=”content”>的标签的。继续往上我们可以看到一个叫做<div class=”article block untagged mb15″ id =….>的标签。

<div class=”article block untagged mb15″ id =….>

点击旁边的小三角,合并标签里面的内容之后我们可以看到有非常多这样格式的标签。而且每一个标签都对应了一个段子。

有很多这样的标签

所以很显然,我们只要把这样的标签都提取出来,我们就可以得到糗事百科中的段子了。

所以现在我们明确了方向——把所有class为article block untagged mb15的div标签找到,然后获取里面span标签的内容。这就是我们要找的段子了。

那要怎么写呢?

首先我们把我们需要的内容转换到Beautiful soup中。

# 引入Beautiful Soup包
from bs4 import BeautifulSoup  

# 把刚刚保存在content中的文件放入Beautiful Soup中
soup = BeautifulSoup(content, 'lxml')

你可能会奇怪后面的’lxml’是什么意思。其实这是因为我们的content是一个字符串(string)数据,它并不是网页文件。所以这里我们告诉Beautiful soup说你就把他当一个网页文件来处理就行。

到目前为止, 我们已经把网页的内容放入了Beautiful soup中,接下来就是施展Beautiful soup的魔法,把网页分成一块块的内容了。

施展魔法

注意,在进行下一步之前你应该确认一下目前我们的代码是这样的:

import requests
from bs4 import BeautifulSoup


headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'}
r = requests.get('http://www.qiushibaike.com', headers = headers)
content = r.text
soup = BeautifulSoup(content, 'lxml')

还记得吗?我们分析的结果是说所有的段子都在网页中class为article block untagged mb15的div标签中的span标签中。看起来好像有点复杂,没事,我们一步一步来做。

首先我们分解出所有class为article block untagged mb15标签:

divs = soup.find_all(class_ = 'article block untagged mb15')

我们可以打印出divs看看是什么样子的。

print(divs)

divs

可以看到,所有div的标签都已经储存在divs里面了。

在进行下一步之前我们再确认一下,这个时候你的代码应该是这个样子的:

import requests
from bs4 import BeautifulSoup


headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'}
r = requests.get('http://www.qiushibaike.com', headers = headers)
content = r.text
soup = BeautifulSoup(r.text, 'lxml')

divs = soup.find_all(class_ = 'article block untagged mb15')
print(divs)

接下来我们要做的事情就是把这些div里面的span都取出来。

我们先把最后一行去掉,避免不必要的打印。然后提取出每个div里面的span

for div in divs:
    joke = div.span.get_text()
    print(joke)
    print(‘------’)

注意,这个时候你的代码看起来应该是这样的:

import requests
from bs4 import BeautifulSoup


headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'}
r = requests.get('http://www.qiushibaike.com', headers = headers)
content = r.text
soup = BeautifulSoup(r.text, 'lxml')

divs = soup.find_all(class_ = 'article block untagged mb15')

for div in divs:
    joke = div.span.get_text()
    print(joke)
    print('------')

这段代码的意思就是把divs中的每个div都取出来(还记得吗,divs里面有所有的class为article block untagged mb15的div)。取出来之后我们对它说,你把你里面叫做span的标签的文字都给我吧。于是我们就把这里面的笑话都放进了joke当中,并打印了出来。

运行一下程序,可以看到你已经成功把糗事百科首页上面的所有段子爬下来了!

你成功了!

恭喜你已经成功入门了python的爬虫。你现在可以给自己鼓鼓掌::__IHACKLOG_REMOTE_IMAGE_AUTODOWN_BLOCK__::12

爬虫并没有那么难,对吗?

one more thing

我们把打印的内容拉到最后面,发现一些读不通的语句:

第三行不知道在说什么

我们回到糗事百科的首页看看

原来是有图片

原来是有图片,怪不得光看字看不懂。那我们的爬虫现在还不能爬下来图片,那有没有什么办法让这些莫名其妙的话都删掉呢?

我想你也应该想到了,同样的,我们去分析网页的结构,然后告诉python,如果存在带有图片的网页结构,我们就不打印这个段子。

我们打开开发者工具。(你还记得快捷键吗?)然后右键点击图片,审查元素。

打开开发者工具

我们看到,图片的网页标签(HTML tag)是img,所以我们只要在代码中检测div中是否有img标签,我们是不是就知道是不是要打印这条段子了呢?

别急,我们先看看把div里面的img都打印出来是什么样子的。

for div in divs:
    # 在这里我们找到所有的img标签,然后打印
    print(div.find_all('img'))  
    joke = div.span.get_text()
    print(joke)
    print('------')

几乎每一条里面都有图片

看来不行, 我们看到几乎每一条里面都有图片。我们回到chrome再看看div里面的结构,原来图片不只段子里面的图片,还包括了发帖人的头像,评论区的点赞图标等等。

那看来仅仅是看是否img标签来判断还是不够的,那我们就只好再看看别的结构,看看有没有更有效的结构。

段子图片的img标签再往上找几层我们不难发现,它们都存在一个<div class=”thumb”>的标签之下。而那些没有图片的段子就没有<div class=”thumb”>这个标签。

所以你成功发现了解决的方法——只要找每个总的div里面是否有<div class=”thumb”>标签就知道这个段子里面是否包含图片了。

那我们继续修改我们的循环:

for div in divs:
    if div.find_all(class_ = 'thumb'):
        continue
    joke = div.span.get_text()
    print(joke)
    print('------')

if div.find_all(class_ = 'thumb')的意思就是检查div.find_all(class_ = 'thumb')中有没有找到class为thumb的标签,如果找到了,那就说明我们不打印这一段,所以执行continuecontinue的意思就是跳到循环末尾,直接进入下一层循环,中间的代码都不要了,所以我们的代码最后看起来是这样的:

import requests
from bs4 import BeautifulSoup


headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'}
r = requests.get('http://www.qiushibaike.com', headers = headers)
content = r.text
soup = BeautifulSoup(r.text, 'lxml')

divs = soup.find_all(class_ = 'article block untagged mb15')

for div in divs:
    if div.find_all(class_ = 'thumb'):
        continue
    joke = div.span.get_text()
    print(joke)
    print('------')

运行一下,可以发现我们已经已经看不到有图片的段子了。

非常的干净

恭喜你,你已经学会了如何去分析网页的结构,这是非常重要的一步,学会分析的方法比学会任何工具都要有用。

而且你做的非常好!你现在可以去喝一杯咖啡,吃点饼干,休息一下。

如果你精力还很充沛,不妨思考一下,能不能把每个段子的作者,点赞数,评论数都提取出来呢?

举一反三:爬取我网站所有的文字title

import requests
from bs4 import BeautifulSoup


headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'}
for num in range(1,19):
        print("Page",num)
        url='http://teach.shconstant.cn/?paged=%d' %(num)
        r = requests.get(url, headers = headers)
        content = r.text
        soup = BeautifulSoup(content, 'lxml')

        divs = soup.find_all(class_='entry-title')

        for div in divs:
                article = div.a.get_text()
                print(article)
                print('------')