正则表达式在python中的使用

正则表达式,Regular Expression,是平时编程中经常用到的一种技术,用于匹配字符串。在爬虫、自然语言处理、文本分析里面经常会用到。严格来说,它可以看作是一种特殊的“编程语言”了,搞懂它,无论是java,python,php,都能轻松的应用。

关于正则表达式,教程有一大堆,也算是一项基本技能吧,想要熟练的掌握,那就得多写,说到底也是一种工具罢了。下面这些教程和文章都讲的比较清楚:

菜鸟教程:http://www.runoob.com/regexp/regexp-syntax.html

30分钟入门:http://deerchao.net/tutorials/regex/regex.htm

关于正则表达式,我这里不多说,有了正则表达式的基础,我就来整理一下python下正则表达式的用法,主要用例子来说明,争取做到拿来就能用。

在python中,正则表达式模块是re模块,使用直接import re即可。

1.基本函数

1.1 re.match函数

1.1.1 语法

re.match(pattern, string, flags=0)

1.1.2 参数

参数 描述
pattern 匹配的正则表达式
string 要匹配的字符串。
flags 标志位,用于控制正则表达式的匹配方式,如下表

可以使用多个标志位,用或连起来就可以,比如re.I | re.M,貌似也是这两个标志位比较常用。

标志位 描述
re.I 使匹配对大小写不敏感
re.L 做本地化识别(locale-aware)匹配
re.M 多行匹配,影响 ^ 和 $
re.S 使 . 匹配包括换行在内的所有字符
re.U 根据Unicode字符集解析字符。这个标志影响 \w, \W, \b, \B.
re.X 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。

1.2 re.search函数

1.2.1 语法

re.search(pattern, string, flags=0)

1.2.2 参数

re.match

1.3 re.findall函数

返回匹配的所有结果,是一个list

1.4 处理返回对象

1.4.1 用法

使用re.match或者re.search方法,如果没有匹配,返回None,如果匹配到了,则返回一个对象,可以通过以下方法对对象进行操作。

返回对象属性方法

1.5 match和search的区别

re.match只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None;而re.search匹配整个字符串,直到找到一个匹配。

2. python例子

2.1 抓取网页中所有超链接

用这个正则表达式可以匹配超链接:<a.*?href=.*?<\/a>

(注释:这里,.是另一个元字符,匹配除了换行符以外的任意字符。*同样是元字符,不过它代表的不是字符,也不是位置,而是数量——它指定*前边的内容可以连续重复使用任意次以使整个表达式得到匹配。因此,.*连在一起就意味着任意数量的不包含换行的字符。)

代码如下:

import re
import urllib
url = "http://www.lookfor404.com"
result = urllib.urlopen(url).read()
reg = re.compile("<a.*?href=.*?<\/a>") #编译正则表达式

#findall方法
urls = reg.findall(s) #找到所有超链接
#和以下句子等价
#urls = re.findall(reg,s)
print len(urls) #打印超链接数量,urls是一个list

2.2 匹配网页标题

以我的博客为例,匹配网页标题,正则表达式我设置为:<title>.*?</title>,代码如下:

import re
import urllib
url = "http://www.lookfor404.com"
result = urllib.urlopen(url).read().decode('utf-8')
reg = re.compile("<title>.*?</title>") #编译正则表达式

#search方法
title = re.search(reg,result) #找到标题,匹配到第一个就行
#和以下句子等价
#title = reg.search(result)
print title.group(0) #打印匹配的内容

最后输出的结果是:
<title>找不到的博客 – 李鹏飞在这里写字</title>

2.3 找到特定的内容

仍然是2.2的例子,这次我不要html标签,只要标题,代码如下,其实只是正则表达式里面加了个括号:<title>(.*?)</title>

import re
import urllib
url = "http://www.lookfor404.com"
result = urllib.urlopen(url).read().decode('utf-8')
reg = re.compile("<title>(.*?)</title>") #编译正则表达式

#search方法
title = re.search(reg,result) #找到标题,匹配到第一个就行
#和以下句子等价
#title = reg.search(result)
print title.group(0) #打印匹配的内容
print print title.group(1) #打印匹配的内容,去除html

输出两行结果:
<title>找不到的博客 – 李鹏飞在这里写字</title>
找不到的博客 – 李鹏飞在这里写字

而在2.2中,如果执行print print title.group(1)则会出错。

2.4 匹配菜单内容

抓取网页里面的所有目录,查看网页源代码,我用这样的正则表达式来匹配:<li id=”menu-item-.*?<a href=.*?>(.*?)</a></li>
,其中的(.*?)就是我要提取的文字。代码如下:

import re
import urllib
url = "http://www.lookfor404.com"
result = urllib.urlopen(url).read().decode('utf-8')
reg = re.compile('
<li id="menu-item-.*?<a href=.*?>(.*?)</a></li>

') #编译正则表达式

#findall方法
menu = re.findall(reg,result) #找到标题,匹配到第一个就行
#和以下句子等价
#menu = reg.findall(result)
#打印menu
for item in menu:
    print item

最后输出如下:

程序输出

似乎输出的不完整,哦,因为有多级菜单,li标签里面还有li标签,这里只抓取了嵌套在最里面的菜单,仅作为用法例子,就不深究了。

以上例子属于比较常用的例子,并没有涉及到一些复杂的用法,之后碰到更多再继续更新上来。

python的map函数的用法

作为python的常用内置函数之一,map函数在某些场景解决问题确实方便,今天就整理一下map函数的用法。

函数官方解释

def map(function, sequence, *sequence_1): # real signature unknown; restored from __doc__
“””
map(function, sequence[, sequence, …]) -> list

Return a list of the results of applying the function to the items of
the argument sequence(s). If more than one sequence is given, the
function is called with an argument list consisting of the corresponding
item of each sequence, substituting None for missing values when not all
sequences have the same length. If the function is None, return a list of
the items of the sequence (or a list of tuples if more than one sequence).
“””
return []

看上去还是挺好理解的,加上一些例子就没问题了。

参数

function:一个函数

Sequence:列表

*sequence_1:多个列表

返回值

一个列表list

作用

①传入一个函数,一个列表的情况

将函数作用于列表中的每一个元素,最终返回作用后的结果列表。

相当于[function(x) for x in sequence]

②传入一个函数,多个列表的情况

取不同列表相同位置的元素,传入函数中进行处理,最终返回作用后的结果列表。

例子

①传入一个函数,一个列表的情况

上代码:

list1 = [1, 2, 3, 4]
def incre(x):
    return x+1
result_list = map(incre,list1)
print result_list

输出结果为[2, 3, 4, 5]

②结合匿名函数lambda使用

以下代码的效果和例子①是一样的:

list1 = [1, 2, 3, 4]
result_list = map(lambda x:x+1,list1)
print result_list

lambda函数,这是python的匿名函数,这里先不多说了。

③传入一个函数,多个列表的情况

先上代码:

list1 = [1, 2, 3, 4]
list2 = [2, 3, 4, 5]
list3 = [3, 4, 5, 6]
def addition(x,y,z):
    return x+y+z
result_list = map(addition,list1,list2,list3)
print result_list

输出结果是[6, 9, 12, 15]

看数字,可以感觉得出来,6=1+2+3,9=2+3+4,12=3+4+5,15=4+5+6,这个时候再看看我在上面的解释:

取不同列表相同位置的元素,传入函数中进行处理,最终返回作用后的结果列表

就比较好理解了,这相当于并行处理。

这里要注意两点:

1.传入的列表(或元组)的数目,要和函数参数的数目一致,比如例子③里面函数addition就需要三个参数,这个时候得传三个列表进去。

2.不同列表的长度要一致,且对应元素的类型要相同。

④传入None

这种情况,直接传入None作为函数,看代码:

list1 = [1, 2, 3, 4]
list2 = [2, 3, 4, 5]
result_list = map(None, list1)
print result_list
#输出结果:[1, 2, 3, 4]
result_list = map(None, list1, list2)
print result_list
#输出结果:[(1, 2), (2, 3), (3, 4), (4, 5)]

如果只传一个列表进去,那就原封不动的返回,如果传多个列表,则效果和zip()函数是一样的,返回一个大列表,里面是多个列表相同位置元素组合出来的元组。

python的切片(slice)机制

在python编程中,我们经常要取list或者tuple里面的元素,或者对字符串进行处理,这恰恰也是python的一个闪光点,它的切片(slice)机制,使得我们处理这类问题变得简单灵活。

“切片”是针对列表(list)、元组(tuple)的,另外,字符串也可以当作特殊的list,通过切片,可以很轻易的实现截取字符串、反转字符串的操作。

所谓的切片,就是按照你的规则,将一个列表切成另外一个列表,先来直观感受一下。

list = ["lookfor404","lipengfei","hello","what"]
list1 =list[0:2:2]
print list1

你猜输出什么?输出了一个新的列表['lookfor404']

假如是这样呢?又会输出什么?

list = ["lookfor404","lipengfei","hello","what"]
list1 =list[0:2:1]
print list1

答案是['lookfor404', 'lipengfei']

现在就来解释一下。切片的使用就是这么简单,在一个list后面加个中括号,里面有两个冒号,插空有3个参数。

list[first_index:last_index:step]

这个表示,从这个list的first_index开始,到last_index结束(不包括last_index本身),以step步长来截取这个list

回到刚才的例子,由于list = ["lookfor404","lipengfei","hello","what"],这个list有4个元素,而list1 =list[0:2:2]表示的是,从list的第0个元素(lookfor404)开始,到第2个元素(hello)结束,以步长2来截取list,这里要注意的是,前两个参数0和2,在数学上是个半闭半开的区间:[0,2)。所以,很自然的,可以得到list1的结果就是['lookfor404']

当然,并不是一定要用两个冒号,如果只使用了一个冒号,就意味着步长默认为1。以上是常规用法,下面拓展一下,其实它很强大,所以python不需要一些字符串的截取函数了。

比如————
复制list,可以直接这样

list = ["lookfor404","lipengfei","hello","what"]
list1 =list[:]

list1就复制了list的内容过来了,因为冒号两边不填参数的话,就默认从0到最后一个元素(包括最后一个元素)。

再比如————
反转字符串,可以这样

str = "helloworld"
str1 =str[::-1]

此时输出str1就是'dlrowolleh'。原因在于步长参数,我们选择了负数,没错,负数就表示反着来,-1表示倒序,-2表示隔着步长2倒序,也就是说,如果str1 =str[::-2],那么输出str1为'drwle'

最后对边界进行一个总结,直接上代码:

s = ["a","b","c","d"]       # s的上界为0,下界为 4(事实上,最后一个元素的索引为3,但这里称4为下界)
s[-100:100]       #返回 [1,2,3,4] :-100超出了上界,100超出了下界:等价于 s[0:4]
s[-100:-200]      #返回 [] :-100,-200均超出了上界,自动取上界:等价于s[0:0]
s[100:200]        #返回 [] :100,200均超出了下界,自动取下界值:等价于s[4:4]
s[:100]           #返回 [1,2,3,4] :开始值省略表示从第0个开始,等价于s[0:4]
s[0:]             #返回 [1,2,3,4] :结束值为空表示到最后一个结束,等价于s[0:4]

运行ggplot出现问题:no display name and no $DISPLAY environment variable

最近在用django做一个项目,关于数据可视化的。作图方面,之前用过百度的echart,这个是在前端直接调用的,这次想尝试在后台直接生成图,然后存在服务器上,之后前端再把图片显示出来就好了。

用的ggplot。

在windows下,用runserver测试,没问题。但是在服务器上,就跑不了了,出现了这样的提示:

tkinter.TclError: no display name and no $DISPLAY environment variable

查看了一下debug信息,发现貌似和Matplotlib有关,继续顺藤摸瓜。

找到了问题所在:http://matplotlib.org/faq/howto_faq.html#howto-webapp

官网的英文解释:

Many users report initial problems trying to use maptlotlib in web application servers, because by default matplotlib ships configured to work with a graphical user interface which may require an X11 connection. Since many barebones application servers do not have X11 enabled, you may get errors if you don’t configure matplotlib for use in these environments. Most importantly, you need to decide what kinds of images you want to generate (PNG, PDF, SVG) and configure the appropriate default backend. For 99% of users, this will be the Agg backend, which uses the C++ antigrain rendering engine to make nice PNGs

不想读完也没关系,大概说下怎么回事。

不同的系统有不同的用户图形接口,默认的接口在windows下跑是没有问题的,问题是我们很多的webapp都不在windows上跑,一般在linux上面,所以要更改它的默认配置,把模式更改成Agg。

纯代码解决方案

这也是大部分人在网上诸如stackoverflow的问答平台得到的解决方案,在引入pyplot、pylab之前,要先更改matplotlib的后端模式为”Agg”。直接贴代码吧!

# do this before importing pylab or pyplot
Import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot asplt

更改配置文件方案

就我的情况而言,上面那种解决方案似乎不适用,因为我是在django中使用的ggplot,它调用matplotlib的时机,我并不是很清楚。所以试了几次,都没有能把matplotlib.use('Agg')这句代码放在正确的位置上,因而也解决不了错误。

所以我转向了另一种解决方案,也是从官网启发而来。

好了,这里告诉我们,什么是backend:

http://matplotlib.org/faq/usage_faq.html#what-is-a-backend

然后这里告诉我们,怎么改配置文件:

http://matplotlib.org/users/customizing.html#customizing-matplotlib

看到下面这个地方:

matplotlibrc文件

没错,它的配置文件就是matplotlibrc,在哪里呢?不同系统不一样,我的系统是ubuntu,运行了命令whereis matlotlibrc,找到了。

matplotlibrc文件位置

编辑一下:

sudo vim /etc/matplotlibrc

找到backend这里,然后将其改成Agg,如下!

# The default backend; one of GTK GTKAgg GTKCairo GTK3Agg GTK3Cairo
# CocoaAgg MacOSX Qt4Agg Qt5Agg TkAgg WX WXAgg Agg Cairo GDK PS PDF SVG
# Template.
# You can also deploy your own backend outside of matplotlib by
# referring to the module name (which must be in the PYTHONPATH) as
# ‘module://my_backend’.
backend : Agg

保存,运行,错误消失。

在linux服务器上部署django项目

不得不承认,django自带的runserver功能确实强大,使得我们在开发的过程中可以轻松的在本地调试、查看网站。但开发是开发,做的项目总归是要部署到服务器上面去的,最近把项目部署到linux上了,这里记录一下。

官方文档:https://docs.djangoproject.com/en/1.9/howto/deployment/

我是参照着文档部署的。下面是我的环境:

ubuntu14.04.3
python2.7.6
apache2.4.7
django1.9.7
mod_wsgi4.5.3

接下来进入配置环节。

安装python-dev

默认情况下,ubuntu系统是安装了python的,不过如果只用这个python,在后面的部署过程就会出问题,先查看你的python版本,不出意外的话是2.7.x,然后额外安装一个对应的dev版。

apt-get install python2.7-dev

安装django

这个很简单,直接pip安装就行,选择好你要的版本。

pip install Django==1.9.7

安装python的mysql驱动

如果你的数据库用的是mysql,还得安装这个。

sudo apt-get install python-mysqldb

安装Apache

在ubuntu系统上,Apache是预装了的,但是它不支持apxs的,对于之后要安装的wsgi组件来说,apxs是必须的。还得安装一个Apache的dev版。先确认你已经安装的Apache版本,我的是Apache2,所以直接运行以下命令继续安装dev版:

sudo apt-get install apache2-dev

安装编译mod_wsgi

有了上述的准备工作之后,安装mod_wsgi就不是什么问题了。

这个是官方文档:https://modwsgi.readthedocs.io/en/develop/user-guides/quick-installation-guide.html

我记录一下我安装编译的步骤。

先去下载源代码:https://github.com/GrahamDumpleton/mod_wsgi/releases

我下载的是mod_wsgi-4.5.3.tar.gz。

然后上传到服务器,解压:

tar xvfz mod_wsgi-X.Y.tar.gz

PS:X,Y为你实际的文件版本,跟文件名一致即可。

解压之后,cd到对应的目录。然后运行

./configure

./configure命令
然后继续运行:

make

最后运行(PS:这一步是需要管理员权限的):

sudo make install

安装成功:
mod_wsgi安装成功
注意图片的第一行,说的是mod_wsgi安装的路径;还有最后一行,要给mod_wsgi.so加上对应的权限。

sudo chmod 644 /usr/lib/apache2/modules/mod_wsgi.so

配置mod_wsgi到Apache上

安装成功之后,要更改一下Apache的配置文件。编辑(不同系统下Apache配置文件不一样,有的是httpd.conf)

sudo vim /etc/apache2/apache2.conf

在文件的最后一行,加上
LoadModule wsgi_module /usr/lib/apache2/modules/mod_wsgi.so
其中第三个参数,是刚才安装完的mod_wsgi.so对应的路径。

部署django项目

django项目刚开始的时候,我们一般都是运行startproject命令来生成项目,这样做有一个好处,django会帮我们自动生成wsgi的配置文件。

首先把本地的django项目复制到服务器上,一般是/var/www/html下,以你Apache的配置为准。

然后我们继续编辑Apache的配置文件:

sudo vim /etc/apache2/apache2.conf

在最后,添加如下内容:

WSGIScriptAlias / /path/to/mysite.com/mysite/wsgi.py
WSGIPythonPath /path/to/mysite.com

Require all granted< /Files>

要注意,/path/to/mysite.com/ 这个路径不要搞错,就是你项目的路径。

配置完毕,重启Apache。

sudo apachectl restart

访问网站
直接输入ip地址访问网站,发现访问不了,查看了一下Apache的error log:

from django.core.wsgi import get_wsgi_application
ImportError: No module named ‘django.core.wsgi’

后来发现,原来是权限问题,无法执行django里面的文件。心累。。

加个权限就行了。

cd /usr/local/lib/python2.7/dist-package
sudo chmod a+x django

ok,项目就这么部署完毕了,如果你是正式的项目,记得把django的debug模式关掉。

python获取网页正文:goose-extractor

做项目要用到提取网页的正文内容,于是在网上找找有什么好用的库。先测试一下,发现python就有现成的库,叫做goose-extractor。

事不宜迟,开始安装吧。

pip install goose-extractor

什么?出现问题了。

在安装依赖的lxml的时候,出现了以下的错误提示:

Microsoft\\Visual C++ for Python\\9.0\\VC\\Bin\\amd64\\cl.exe’ failedwith exitstatus 2

找不到好的解决方案,最后祭出大杀器。直接用编译好的。

先打开http://www.lfd.uci.edu/~gohlke/pythonlibs/

ctrl+F搜索lxml,然后下载它对应的whl文件,下载之后pip install xxx.whl即可安装!

安装完这个比较难缠的lxml之后,就方便了,重新pip install goose-extractor

等待安装成功。

使用非常简单,给出一个实例代码,抓取《https://www.lookfor404.com/简易python爬虫–爬取冷笑话/》的正文内容。

# -*- coding: utf-8 -*-
from goose import Goose
from goose.text import StopWordsChinese
url  = 'https://www.lookfor404.com/%e7%ae%80%e6%98%93python%e7%88%ac%e8%99%ab-%e7%88%ac%e5%8f%96%e5%86%b7%e7%ac%91%e8%af%9d/'
g = Goose({'stopwords_class': StopWordsChinese})
article = g.extract(url=url)
print article.cleaned_text

效果不错,截取一下控制台的输出:
goose-extractor提取网页正文结果

但是速度并不快,具体里面怎么实现的还没看,但是它要用到python的结巴分词,估计这个耗时久一点,另外还有一个问题,就是我<code>标签里面的内容,被过滤掉了,所以抓取技术文章,有点不合适,哈哈。

记录一下windows下panda的安装过程

搞大数据开发,就是和数据打交道,具体怎么处理数据,也需要深究,python有很多很好用的库,其中pandas是比较出名的。下面是简单的介绍:

Pandas是python的一个数据分析包,最初由AQR Capital Management于2008年4月开发,并于2009年底开源出来,目前由专注于Python数据包开发的PyData开发team继续开发和维护,属于PyData项目的一部分。Pandas最初被作为金融数据分析工具而开发出来,因此,pandas为时间序列分析提供了很好的支持。 Pandas的名称来自于面板数据(panel data)和python数据分析(data analysis)。panel data是经济学中关于多维数据集的一个术语,在Pandas中也提供了panel的数据类型。

官网:http://pandas.pydata.org/

我刚开始以为直接pip就能安装pandas了。事实证明我太天真了。下面还是记录一下我的安装过程吧。

利用pycharm直接安装pandas

pycharm的包管理工具挺好用的,直接可以在里面搜索到很多可以用的包,然后直接安装。安装pandas也不例外。

首先打开pycharm,直接在setting里面找到编译器的选项

pycharm中编译器设置

可以看到本地已经安装的包,点击右边的+号,就可以安装新的包了。想安装pandas,直接搜索pandas就是了,记得别少了s,是pandas

搜索后安装即可。pandas是依赖于别的包的,根据提示,如果还需要安装别的包,继续搜索安装就行。

在windows下用pip安装pandas

官网推荐的是直接使用Anoconda,它集成了pandas,可以直接使用。安装挺简单的,有windows下的安装包。

如果不想安装庞大的Anoconda,那就一步一步用pip来安装pandas。

如果直接pip install pandas,可能会出现一系列的问题,为了稳妥起见,我先把依赖包都一个个安装下来,最后再安装pandas,就没有问题了。

官网说pandas需要以下几个依赖:

setuptools
NumPy: 1.7.1 or higher
python-dateutil 1.5 or higher
pytz

一个个来。首先setuptools,这个和pip本身在新一点的python下是默认安装了的。我的python版本是2.7.11

然后pip install numpy

安装numpy

安装到一般,居然出错了。错误如下:

Microsoft Visual C++ 9.0 is required Unable to find vcvarsall.bat

原因:windows下使用pip安装包的时候需要机器装有vs2008,VS2012都不行,如果不想装VS2008的话,可以安装一个Micorsoft Visual C++ Compiler for Python 2.7的包。
下载地址:http://www.microsoft.com/en-us/download/details.aspx?id=44266

如果还是不行的话,就直接去官网找windows下的安装包。

官网:http://sourceforge.net/projects/numpy/files/

我找的1.9.2版本:http://sourceforge.net/projects/numpy/files/NumPy/1.9.2/

有windows下针对python2.7的安装包,文件名为numpy-1.9.2-win32-superpack-python2.7.exe

下载安装即可。

搞定了numpy,接下来安装python-dateutil,命令:pip install python-dateutil
很顺利。

然后安装pytz,命令:pip install pytz
也很顺利。

最后!直接安装pandas就行了,命令:pip install pandas

安装pandas

大功告成,真是折腾。

python爬虫–抓取ajax更新的内容

虽然很多情况下,直接对一个url发出请求,就能得到页面的源代码,但是我们还得考虑这么一种情况,就是有的网站为了用户体验,采用了ajax技术–即不刷新页面而改变页面内容,那么我们该怎么获取这些内容呢?

从本质出发,ajax技术也就是这么一个过程:利用js来发起一个post请求–然后接收返回的数据–js改变页面内容。在大多数情况下,我们都是点击一个按钮,然后页面就更新了。从原理上来看,点击某个按钮,就会发送一个post请求(当然还有滚动鼠标发送请求的),服务器再返回结果,再将结果直接更新到页面上。我们的思路是–用爬虫发送同样的post请求,从而获得返回来的数据。

简单点说,对于爬虫而言,在获取数据上,get和post并无太多异样,只是get的话,我们只需要对某个链接发送请求就行,而post,则需要一些表单内容。那么,我们怎么知道该post什么内容过去,又能获得什么响应呢?

以虎嗅网(http://www.huxiu.com/)为例,我用python写了一个爬取它利用ajax更新的数据。

我们打开它的首页,拉到最下面,发现有个“点击加载更多”的按钮,颜色很浅。

点击它,就会有新的内容出现。

虎嗅网底部

我们要做的,就是看看点击这个按钮会发生什么。

我这里以遨游浏览器为例,大多数浏览器(比如chrome,Firefox)应该都有类似的功能,在网页上,点击右键–审查元素,在弹出的窗口菜单栏点击“网络”一栏,在这里,我们可以监控一些get,post等行为。

刚开始是空的。

此时,回到网页上去,点击“点击加载更多”按钮。然后再回到开发者工具那里,会发现,出现了很多的请求:

点击加载更多

其中,有个请求的方法是POST,这就是我们要找的。点进去,可以看到http报头、预览、响应和cookies。

从中,我们可以获得一些头部信息,以及post的表单数据。

  • 头部信息

不同网站对头部信息的要求不同,有的不需要头部信息都能响应,有的则需要头部信息的一部分,虎嗅是不需要头部信息的。

  • 表单数据

从开发者工具里面看,虎嗅需要的表单数据有两个,一个是’huxiu_hash_code‘,这个是用来防止csrf的,一个是page,代表页数,我们把huxiu_hash_code复制下来。

构造一个请求过去,会得到一个json格式的响应,内容有msg,total_page,result和data,要的文章数据在data里面。我的程序把data里面的标题都提取出来,打印到屏幕下,下面贴上代码:

#-*- coding:utf-8 -*-
__author__ = '李鹏飞'
import urllib2
import urllib
import json
import re
url = 'http://www.huxiu.com/v2_action/article_list'
user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
data = {'huxiu_hash_code':'322cf76eec54c4e275d7c8122028a3b2','page':2}
data = urllib.urlencode(data)
try:
    request = urllib2.Request(url=url,data=data)
    response = urllib2.urlopen(request)
    #解析json
    result = json.loads(response.read())
    print result['msg']
    content = result['data']
    #匹配文章标题
    pattern = re.compile('
<div class="mob-ctt">.*?target="_blank">(.*?)</a>',re.S)
    #虎嗅文章标题
    items = re.findall(pattern,content)
    for item in items:
        print item
except urllib2.URLError, e:
    if hasattr(e,"code"):
        print e.code
    if hasattr(e,"reason"):
        print e.reason

这样子的话,大部分网页就能抓了,管他是post还是get。

Linux下升级python版本-2.7.9

我用的centos,预装着python,版本比较低,是2.6的,而2.7.9以上的版本自带pip,easy_instll和wheel,为了省去安装这些东西的麻烦,我干脆直接重新装一个python2.7.9好了。

这里记录一下升级安装的过程。

首先去官网下载对应的python的tgz文件,在linux下可以直接wget,在windows下直接下载就行,网址:https://www.python.org/ftp/python/2.7.9/Python-2.7.9.tgz

若网址失效,请移步python官网下载。

在linux下,将下载的tgz文件解压到指定目录,运行以下命令:

tar -xvzf Python-2.7.9.tgz -C /home 

解压完成,切换到解压的目录下,运行命令:

./configure

这个命令完成后,会生成makefile文件,供下一步使用。

接下来编译源代码,运行命令:

make

再运行最后一个命令,自动把文件分发的系统的关键位置,命令如下:

make install

等到处理完成,不需要任何更改,直接在命令行输入python,看到如下提示即安装成功:
python升级成功

2.7.9以及以上版本的python都预装了pip,eazy_install,很是方便,之后安装别的包就比较简单了。

简易python爬虫–爬取冷笑话

今天闲来无事,就想接触一下爬虫的基本知识,搞大数据,没有数据是万万不可的,而数据的来源,有很大一部分是通过网络爬虫来抓取的。

关于爬虫,似乎python的框架会比较多,比如scrapy。不过刚开始接触,用python一些原生的库就可以完成一些简单的小爬虫程序了,就比如我刚刚写的冷笑话爬虫。

在这之前,得大概搞清楚爬虫的工作原理,其实也很简单。首先是对你的目标网站发出http请求,目标网站会返回一个html文档给你,也就是我们俗称的网页,原本,这些文档都是由一大堆标签构成的,但浏览器配合上css,js,就形成了我们看到的各种各样的网页。但我们的爬虫不需要这些外衣,我们只需要内容,所以爬虫接下来的工作是处理这个html文档,提取我们要的信息,然后继续爬取下一个网页,如此往复循环。

不多说,先上我写的一段python脚本,爬取<我们爱讲冷笑话>的随机页面,网址为:http://lengxiaohua.com/random,这个页面每次刷新都有20则不同的随机冷笑话,我的程序功能是:输入数字1到20看对应的笑话,输入new重新爬取另外20则新笑话,输入quit退出程序。代码如下:

#-*- coding:utf-8 -*-
__author__ = '李鹏飞'
import urllib2
import re

class randomJoke:

    #初始化方法
    def __init__(self):
        self.url = 'http://lengxiaohua.com/random'
        self.user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
        #初始化headers
        self.headers = { 'User-Agent' : self.user_agent }
        #笑话内容
        self.content = []

    #获取网页源代码
    def getSourceCode(self):
        try:
            request = urllib2.Request(url = self.url, headers=self.headers)
            response = urllib2.urlopen(request)
            sourceCode = response.read().decode('utf-8')
            return sourceCode
        except urllib2.URLError, e:
            if hasattr(e,"reason"):
                print u"网络错误...",e.reason
                return None

    #获取笑话
    def setContent(self):
        sourceCode = self.getSourceCode()
        if not sourceCode:
            print('获取网页内容失败~!')
            quit()
        pattern = re.compile(' <pre.*?js="joke_summary".*?"first_char">(.*?)</span>(.*?)</pre>

.*?class="user_info">.*?<a.*?>(.*?)</a>.*?

(.*?)

',re.S)
        items = re.findall(pattern,sourceCode)
        self.content = items
        print u"已经爬取源代码...正在解析源代码..."

    #返回笑话
    def getContent(self):
        return self.content

    #打印一则笑话
    def printAJoke(self,number):
        joke = self.content[number]
        print u"作者:%s" %(joke[2])
        print u'发表于:'+ joke[3]
        #item[0]和item[1]组成完整的内容
        print joke[0]+joke[1]

randomJoke = randomJoke()
notQuit = True
print u"你好,这里是随机笑话!"
print u"---------------------"
randomJoke.setContent()
print u"...."
print u"笑话池已经装满,20/20"
print u"输入1到20看笑话~~,输入quit退出,输入new重新爬取新笑话"
while notQuit:
    input = raw_input()
    if input == "quit" :
        print u"bye!"
        notQuit = False
    elif input == "new":
        randomJoke.setContent()  #重新抓取笑话内容
        print u"...."
        print u"笑话池已经装满,20/20"
        print u"输入1到20看笑话~~,输入quit退出,输入new重新爬取新笑话"
    else:
        input = int(input)
        randomJoke.printAJoke(input-1)
        print u"--------------------------------------------------"
        print u"输入1到20看笑话~~,输入quit退出,输入new重新爬取新笑话"
print u"您已经成功退出!"
quit()

保存这段脚本,运行,效果如下:

运行冷笑话爬虫

说明一下,有些笑话是带图片的,而我没有抓取图片,仅仅抓取了作者,发表日期,和内容。

下面对程序稍微进行一下解释:

引入了urllib2re两个模块,urllib2是用来发送http请求的,re是正则表达式的相关模块。

randomJoke是程序的主要部分,下面就所有的方法,进行简单的说明。

__init__方法

位置:9-15行

这个是初始化方法,定义了四个变量,url为目标网址;user_agent是我们发送http请求的时候伪造的头部的一个客户端信息;headers是头部信息,这里只把user_agent信息加进去就行了;content[]是一个列表,用来存放笑话内容。

getSourceCode方法

位置:18-27行

这个方法是用来获取网页的源代码的。在第20行,发起一个http请求,接着读取返回的源代码,并return源代码。

setContent方法

位置:30-44行

这个方法是比较关键的一个方法,主要是利用正则表达式匹配源代码,并把我们需要的内容爬取下来。第35-41行代码就是正则表达式的匹配模式,我们要抓取网页中的20个笑话,其中的.*?是匹配任意字符,多一个括号,即(.*?)表示匹配的内容是我们需要的内容,在这个片段当中,出现了4(.*?),因此,程序的第36行,item表示的是一个列表,这个列表的形式是这样的:[[[item1-1],[item1-2],[item1-3],[item1-4]],[…],[…],…[[item20-1],[item20-2],[item20-3],[item20-4]]]。即这个列表有20个元素(代表这个网页的20个笑话),每个元素又是一个列表–列表中有4个元素(代表爬取的4个内容,对应前面说的4个(.*?)。这里附上其中的一小部分网页源代码,对应着网页的html源代码,我们就能感受到这个简单的正则表达式的作用了:


<pre js="joke_summary"><span class="first_char">来</span>表哥家吃饭,小侄子一边看西游记一边手舞足蹈上蹦下跳学猴哥。
我哥:“傻儿子,你是孙悟空吗?”
侄子:“正是,你孙爷爷在此。”
打到现在还在哭。</pre>
</div>
<div class="para_info clearfix">
<div class="user_info">
                        <img src="http://joke-image.b0.upaiyun.com/img/user/cover/630347-299927_40x40.jpg" class="left corner4px" height="40" width="40"/>
                        <a href="/user/630347">离愁▍ Feast aw</a>
                        <p>15天前</p>

一共有20个类似的html片段,正则表达式的作用就是把笑话内容,作者和发表日期提取出来。

printAJoke方法

位置:51-56行

打印一则笑话,在这里我们更能感受到content[]的结构了,因为printAJoke这个方法需要传一个number参数进来,范围是1到20,我们就可以读取对应的笑话了,比如content[0]代表第一则笑话,和java中的数组类似。

53行以后就是程序运行的基本逻辑了,比较简单,就不多解释了。

就这样,一个简单的爬虫就完成了!