- 主要涉及的模块和工具
- 正则表达式(re模块)
主要用来匹配筛选所需要的字符串
基本的使用方法见python笔记:re模块、正则表达式 - requests模块
主要用来对网页发送请求,并处理响应的数据
参考资料:requests官方文档 - BeautifulSoup(bs4模块)
用来简化数据筛选的过程
参考资料:beautifulsoup官方文档 - ie、firefox、chrome的开发者工具
用来分析网页上的数据,包括页面内容、headers、form data等
- 正则表达式(re模块)
requests模块
参考资料:requests官方文档
嗯……据说requests是基于urllib3的第三方库
简单的使用如下:
requests.get获取页面
返回一个response对象
第一个参数为页面的url,可以加入关键词参数cookies
requests.post发送数据
也是返回一个response对象
第一个参数为页面的额url,可以加入关键词参数headers和data
headers即为Requests Headers的内容,参数形式是一个字典
data即为form data的内容,参数形式也是一个字典
response对象的属性
- encoding:页面编码
一般requests会自动推测出编码 - text:以文本的方式存放的页面内容
- content:以字节形式存放的页面内容
- status_code:页面的状态码
可以用来判断是否正常打开页面 - headers:页面的响应头即Response Headers
- cookies:当前的cookies
BeautifulSoup(bs4模块)
参考资料:beautifulsoup官方文档
- BeautifulSoup函数:将文档转换为一个BeautifulSoup对象并返回
参数为一个字符串
文档内容会自动转换为unicode编码
BeautifulSoup对象
一般情况下可以直接当作它的子对象tag对象来使用
其中包含以下子对象
tag对象
对象的名称与html和xml的标签名都相同
- name:即标签的名字
- attrs:即标签内包含的属性
比如class、id、name、type等
可以通过has_attr()方法判断对象内(也就是标签内)是否包含这些属性 - contents:即始末标签间的显示内容
- tag对象内常常还会有tag对象,称为子节点
- children:一个生成器,依次生成直接子节点的内容,可用于迭代
- descendants:一个生成器,一次生成最里层的子孙节点的内容,可用于迭代
比较:children和descendants
假设有一个标签<head><title>aaa</title></head>
通过soup.children
将会获取<title>aaa</title>
整个直接子节点
通过soup.descendants
则会获取aaa
这样的子孙节点
- 与子节点对应的为父节点
- parents:与children类似
- parent:得到某个父节点
- 既然有父有子,那么也会有兄弟节点
- next_sibling:获取下一个同级节点
- next_siblings:迭代获取之后的所有节点
- previous_sibling:获取上一个同级节点
- previous_sibling:迭代获取之前的所有节点
string对象
即尖括号<>
之外的内容
其他
- next_element和previous_element:获取下(上)一个对象,包括tag和string
- next_elements和previous_elements:迭代获取下(上)一个对象
find_all方法
查找标签
直接访问tag对象只能得到第一个匹配结果,如果需要得到所有结果需要借助find_all函数
函数返回一个包含所有该标签的内容的列表- 单个指定
第一个参数为标签名,可以指定关键词参数,筛选属性
如:soup.find('a', id='link3')
- 正则匹配
将一个re.compile的对象作为参数代替标签名
BeautifulSoup将通过match的方式来查找匹配的标签 - 多个指定
将多个标签(字符串)放入列表作为参数传入 - True
传入True
将表示匹配任何值
- 单个指定
find方法
用法与find_all类似- prettify方法
将文档返回为一个有缩进的字符串,便于print输出
无参数 - UnicodeDammit函数(啧啧……这函数名)
自动检测编码,如果能导入chardet
或cchardet
可以大大提高自动检测的准确率
可直接用于任何字符串
返回一个具有以下属性的对象
unicode_markup:自动解码的结果
original_encoding:检测到的编码形式
从网页上爬下所需信息
这个比较简单,从源码中找到所需要的信息,观察它的规则,用正则表达式表达出来并且借助re模块的函数进行匹配提取即可
比如:我要爬下十大的标题——
查看源码:
<a href="dispbbs.asp?boardid=736&id=4508354" target="_blank"><font color=#000066>[CIE创道人生大讲堂]世界那么大,出去创业吧——为你讲述众筹之王的创业之道(抢楼有大礼哦~)</font></a>
<a href="dispbbs.asp?boardid=114&id=4508053" target="_blank"><font color=#000066>真是哔狗了,刚加的妹子被我吓跑了</font></a>
<a href="dispbbs.asp?boardid=100&id=4508066" target="_blank"><font color=#000066>[微创业联盟]创业,就问你约不约!——抢楼有精美礼品相赠哦!</font></a>
......(这里仅列出三条)
可以找到其中的规律,并且用以下正则表达式表示:
<a href="dispbbs\.asp\?boardid=[0-9]+&id=[0-9]+" target="_blank"><font color=#000066>(.+?)</font></a>
- 要对特殊字符“.”和“?”进行转义
- 可以看到“boardid”和“id”接的是一串数字,用
[0-9]+
进行匹配 - 所要提取的部分内容为任意数量的任意字符,而且要防止贪婪匹配,用
(.+?)
进行匹配
然后用re.findall
函数把匹配项都找出来就可以了
接着我们把内容写入文件,比如把包含所有匹配项的titles
写到/home/zk/TopTen
文件中去
import codecs, os
f = codecs.open('/home/zk/TopTen', 'w', 'utf-8')
for i, title in zip( range(1,11), titles ):
f.write( str(i) + title + os.linesep )
f.close()
因为写入的内容包含中文,所以我们需要使用codecs模块的open函数打开文件,指定编码为utf-8
模拟登录
收集信息
借助chrome的开发者工具,可以捕捉到网页的相关信息
登录后,我们只要找到POST了包含用户名信息的页面,就可以找到处理登录的页面
cc98是post到sign.asp
中去
查看这个页面的post信息,主要关注以下的信息
General
Request URL:http://www.cc98.org/sign.asp
#
Request Headers
Accept:text/plain, /; q=0.01
Accept-Encoding:gzip, deflate
Accept-Language:en-US,en;q=0.8
Connection:keep-alive
Content-Length:60
Content-Type:application/x-www-form-urlencoded; charset=UTF-8
Cookie:ASPSESSIONIDQSCRDADB=NMLCLJEDIAGPHCHGOKDBHPGN; upNum=0; aspsky=username=zkkzkk&usercookies=3&userid=509527&useranony=&userhidden=2&password=6cc9bed70c3c80e1; BoardList=BoardID=Show; owaenabled=True; autoplay=True
Host:www.cc98.org
Origin:http://www.cc98.org
Referer:http://www.cc98.org/
User-Agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36
X-Requested-With:XMLHttpRequest#
Form Data
a:i
u:zkkzkk
p:*
userhidden:2
- Request URL:
是这个请求发送的url - Request Headers:
是我们发送的请求头,其中Host
不是自己指定和设置的,http请求会自动处理Content-Type
、User-Agent
、Accept-Encoding
必须有,值照抄即可 - Form Data:
就是我们提交的表单
这里的a
和userhidden
不知道是什么意思,先照搬u
显然是用户名p
则是密码,这里安全起见,我把他用星号代替了,这里其实是一个用hashlib.md5加密了的密码
模拟
利用requests.post
函数即可模仿post
#headers主要照搬Requests Headers的信息,删去Host和Cookie即可
headers = {
'Accept':'text/plain, */*; q=0.01',
'Accept-Encoding':'gzip, deflate',
'Accept-Language':'en-US,en;q=0.8',
'Connection':'keep-alive',
'Content-Length':'60',
'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8',
'Origin':'http://www.cc98.org',
'Referer':'http://www.cc98.org/',
'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36',
'X-Requested-With':'XMLHttpRequest'
}
#payload为一些表单信息
payload = {
'a' : 'i',
'u' : 'zkkzkk',
'p' : '***********',
'userhidden' : '2'
}
r = requests.post('http://www.cc98.org/sign.asp', data=payload, headers=headers)
函数返回一个包含登陆信息cookies的对象r
接下来我们只要用这个cookies去访问cc98上的页面即可
rr = requests.get('http://www.cc98.org', cookies=r.cookies)
练(wan)习(wan):模拟登录10.71.45.100并爬下C大程的课件
先上源码——
# coding: utf-8
import requests,sys,bs4,re,urllib,os,codecs
#设置编码为utf8,不然后边对中文进行url编码的时候会出现奇怪的错误
reload(sys)
sys.setdefaultencoding('utf8')
#登录URL
URL = 'http://10.71.45.100/cstcx/web/login/check.asp?tn=xjc&Rnd.7055475=.533424'
#HEADERS
#这里只去掉了Request Headers中的Host和Cookie,其他照搬
headers = {
'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Encoding':'gzip, deflate',
'Accept-Language':'en-US,en;q=0.8',
'Cache-Control':'max-age=0',
'Connection':'keep-alive',
'Content-Length':'32',
'Content-Type':'application/x-www-form-urlencoded',
'Origin':'http://10.71.45.100',
'Referer':'http://10.71.45.100/cstcx/web/index.asp?tid=27&tt=xjc&tn=%D0%EC%BE%B5%B4%BA&Rnd.5751838=.1000522',
'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36'
}
#FORM DATA
payload = {
'txtUser':'3140100805',
'txtPwd':'123456'
}
#模拟登录
check = requests.post(URL, data=payload, headers=headers)
#“参考资料”URL
rsrcURL = 'http://10.71.45.100/cstcx/web/courceinfo/resource.asp?coid=18&flag=0&coname=C%D7%A8%CC%E215&clsid=25&clsname=C%D7%A8%CC%E215&techid=27&loc=%BF%CE%B3%CC%D0%C5%CF%A2%3E%3E%3E%B2%CE%BF%BC%D7%CA%C1%CF'
#“讲稿”URL
docuURL = 'http://10.71.45.100/cstcx/web/courceinfo/jianggao.asp?coid=18&loc=%BF%CE%B3%CC%D0%C5%CF%A2%3E%3E%3E%BD%B2%B8%E5'
#“参考资料”本地存放目录
rsrcDIR = '/home/zk/C大程资料/Resources/'
#“讲稿”本地存放目录
docuDIR = '/home/zk/C大程资料/Documents/'
def GetHref(URL):
'''
利用模拟登录获得的cookies访问“参考资料”或“讲稿”的页面
获取所需资源的名称和url并返回(字典)
'''
resources = requests.get(URL, cookies=check.cookies)
resources.encoding = 'gb2312' #requests识别出来的是iso,需要手动修改
rsrc_soup = bs4.BeautifulSoup(resources.text) #用BeautifulSoup处理页面内容
rsrc_a = rsrc_soup.find_all('a') #找出页面内容中的a标签
rsrc = {} #初始化一个字典,用于储存name-href键值对
for a in rsrc_a: #逐个处理每个a标签
href = re.findall('href="(.+?)"', str(a))[0] #用正则表达式筛选出文件的url
href = href.replace('../../', 'http://10.71.45.100/cstcx/') #由于页面上写的是相对路径,我们需要将其恢复为绝对路径,以便后边的正常访问
name = href.split('/')[-1] #截取文件名
rsrc[name] = href #存入字典
return rsrc
def Download(rsrc, DIR):
'''
依据本地文件名判断文件是否已经下载过
如果是新文件则下载到本地、打印下载信息
注释掉的代码为原先采用文件记录的形式判断是否重复
'''
#lst = open(DIR + 'list','r')
#lst_name = lst.readlines()
#lst.close()
#lst = codecs.open(DIR + 'list', 'a','utf8')
lst_name = os.listdir(DIR) #获取本地目录的文件名
for name,url in rsrc.items(): #逐个处理所获取的name-href键值对
if name not in lst_name: #如果这个文件不在本地?
#lst.write(name + os.linesep)
urllib.urlretrieve(url, DIR + name) #下载该文件
print 'New file: ' + DIR + name #打印信息
#lst.close()
def main():
rsrc = GetHref(rsrcURL)
Download(rsrc, rsrcDIR)
docu = GetHref(docuURL)
Download(docu, docuDIR)
print 'Finished~'
if __name__ == '__main__':
main()
信息的抓取和登录模拟就跟玩CC98差不多
在这个玩这个练习的时候呢,主要遇到的是页面的编码问题
- requests编码判断错误
requests自带自动识别页面编码的功能,所以没过多的关注页面的编码问题
但是在保存本地文件的时候发现文件名都是乱码
最后发现是它的识别出错了,识别的结果是ISO8859-1
,这是一个英文的编码
查看页面的源码后可以看到head标签下的meta子标签说明了charset=gb2312
所以我们需要手动修改requests对象的encoding属性为gb2312
python编码问题
原先的默认编码是啥我倒是不知道
但是我把取出来的带中文的页面内容作为url去下载文件时会出错,说是不存在页面
后来百度了一下,在程序最前端加入以下代码修改python默认编码即可——import sys reload(sys) sys.setdefaultencoding('utf8')
另外一个问题是如何下载文件,有以下三种方法:
参考资料:python下载文件的三种方法
urllib.urlretrieve直接下载
第一个参数为url,第二个参数为保存的本地路径
如这次的代码urlli.urlretrieve(url, DIR + name)
urllib2.urlopen获取文件内容,以二进制的形式写入本地文件
url = '.....' f = urllib2.urlopen(url) data = f.read() with open('demo2.zip', 'wb') as code: code.write(data)
requests.get获取文件内容,以二进制的形式写入本地文件
url = '....' r = requests.get(url) with open('demo3.zip', 'wb') as code: code.write(r.content)
显然urllib模块的方法最简单