python爬虫初探

  • 主要涉及的模块和工具
    • 正则表达式(re模块)
      主要用来匹配筛选所需要的字符串
      基本的使用方法见python笔记:re模块、正则表达式
    • requests模块
      主要用来对网页发送请求,并处理响应的数据
      参考资料:requests官方文档
    • BeautifulSoup(bs4模块)
      用来简化数据筛选的过程
      参考资料:beautifulsoup官方文档
    • ie、firefox、chrome的开发者工具
      用来分析网页上的数据,包括页面内容、headers、form data等

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函数(啧啧……这函数名)
    自动检测编码,如果能导入chardetcchardet可以大大提高自动检测的准确率
    可直接用于任何字符串
    返回一个具有以下属性的对象
    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>
  1. 要对特殊字符“.”和“?”进行转义
  2. 可以看到“boardid”和“id”接的是一串数字,用[0-9]+进行匹配
  3. 所要提取的部分内容为任意数量的任意字符,而且要防止贪婪匹配,用(.+?)进行匹配

然后用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-TypeUser-AgentAccept-Encoding必须有,值照抄即可
  • Form Data:
    就是我们提交的表单
    这里的auserhidden不知道是什么意思,先照搬
    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差不多
在这个玩这个练习的时候呢,主要遇到的是页面的编码问题

  1. requests编码判断错误
    requests自带自动识别页面编码的功能,所以没过多的关注页面的编码问题
    但是在保存本地文件的时候发现文件名都是乱码
    最后发现是它的识别出错了,识别的结果是ISO8859-1,这是一个英文的编码
    查看页面的源码后可以看到head标签下的meta子标签说明了charset=gb2312
    所以我们需要手动修改requests对象的encoding属性为gb2312
  2. python编码问题
    原先的默认编码是啥我倒是不知道
    但是我把取出来的带中文的页面内容作为url去下载文件时会出错,说是不存在页面
    后来百度了一下,在程序最前端加入以下代码修改python默认编码即可——

    import sys
    reload(sys)
    sys.setdefaultencoding('utf8')
    

另外一个问题是如何下载文件,有以下三种方法:
参考资料:python下载文件的三种方法

  1. urllib.urlretrieve直接下载
    第一个参数为url,第二个参数为保存的本地路径
    如这次的代码

    urlli.urlretrieve(url, DIR + name)
    
  2. urllib2.urlopen获取文件内容,以二进制的形式写入本地文件

    url = '.....'
    f = urllib2.urlopen(url)
    data = f.read()
    with open('demo2.zip', 'wb') as code:
        code.write(data)
    
  3. requests.get获取文件内容,以二进制的形式写入本地文件

    url = '....'
    r = requests.get(url)
    with open('demo3.zip', 'wb') as code:
        code.write(r.content)
    

显然urllib模块的方法最简单