j记录学习--python网络爬虫与信息提取

The website is the API...要获取网站内容,只要把网站当成API就可以了。

requests库获取网页信息---》Beautiful Soup解析提取到信息的内容---》利用re库正则表达式提取其中某部分的关键信息----》Scrapy*网络爬虫j记录学习--python网络爬虫与信息提取_第1张图片

网络爬虫之规则-》requests库

requests库的介绍和使用

requests库的更多信息参考:http://cn.python-requests.org/zh_CN/latest/

requests库的安装

pip install requests   # doc命令行下输入安装即可,测试例子:

>>> import requests                    #导入requests库
>>> r = requests.get(url="http://www.baidu.com",timeout=30)  #返回一个Python对象r
>>> r.status_code 
200                                    #状态码200 Ok
>>> r.encoding = r.apparent_encoding   #
>>> r.text
>>> type(r)                            #查看r类型,返回的是一个对象

requests库的7个常用方法

j记录学习--python网络爬虫与信息提取_第2张图片

get方法:

r = requests.get(url)

r 是服务器返回的一个包含服务器所有资源的Response对象,requests.get(url)是Python构造的一个向服务器请求资源的Request对象。

requests.get(url,params=None,**kwargs)

  • url :要获取的API/网页的URL链接
  • params:URL中的额外参数,可以是字典、字节流格式,可选
  • **kwargs:共有12个控制访问的参数

Response对象的常用属性

  • r.status_code  http请求的返回状态,200是OK
  • r.text  http响应内容的字符串形式,即URL返回的页面内容
  • r.encoding   从http Header中猜测的响应内容的编码方式,若header没有charset字段,则默认为ISO-8859-1编码,
  • r.apparent_encoding  从内容分析出的响应内容编码方式(备选编码)这个更准确解析页面的编码
  • r.content  http响应内容的二进制形式(如图片是由二进制存储的,就可以通过r.content还原这图片)
  • r.headers  http响应的响应头
  • r.raise_for_status http请求状态码不是200则会引发HTTPError异常

爬去网页通用代码框架

网络链接有风险,所以需要进行异常处理。

j记录学习--python网络爬虫与信息提取_第3张图片

requests库的异常:

r.raise_for_status()   #判断r若果不是200,产生异常requests.HTTPError异常

爬去网页通用代码框架:

import requests
def getHTMLText(url):
    try:
        r = requests.get(url,timeout=30)
        r.raise_for_status() #请求不成功不是200,则引发HTTPError异常
        r.encoding = r.apparent_encoding
        return r.text
    except:
        return "异常了!"
if __name__ == "__main__":
    url = "http://www.baidu.com"
    print(getHTMLText(url))

HTTP协议和requests7个方法

http是超文本传输协议,是一个基于请求与响应模式的、无状态的应用层协议。第一次请求和另一次请求没有联系、工作于tcp协议之上。ip--tcp-http..

http协议采用URL作为定位网络资源的标识,协议名://host[:port]/资源路径

一个URL对应服务器一个资源,http协议对资源的操作方法:每次操作都是独立和无状态的

j记录学习--python网络爬虫与信息提取_第4张图片

和requests库的get、post、put、、、7个方法是一一对应的!!!

理解PATCH和PUT的区别:

  • PATCH是仅向URL提交局部更新的字段数据,其他不改的数据不用上传
  • PUT是向服务器上传的字段会覆盖原来服务器的字段,若果只想改变服务器一个字段用PATCH上传该字段即可,若果用PUT则需要把改变的字段和服务器原来的字段全部上传到对应字段,否则会上除掉不上传的字段

PATCH优点是节省网络带宽啊!需要改服务器哪个字段就上传哪个字段就可!

Requests库的head方法:

>>> r = requests.head(url="http://www.jd.com",timeout=30)  #可以以很少的网络流量获取网站大体信息
>>> r.headers                                              #响应头
{'Content-Type': 'text/html', 'Connection': 'keep-alive', ...}
>>> r.text                                                 #所以API内容是空的
''
Requests库的post方法:控制参数是data
向URLpost一个字典,自动编码为form(表单)

>>> payload = {'key1':'value1','key2':'value2'}  #post的是一个字典,则自动编码为form表单
>>> r = requests.post(url='http://httpbin.org/post',data=payload,timeout=30)
>>> print(r.text)   #返回json
{
...... 
  "form": {                                      #post的是字典或键值对,自动放在form表单下的
    "key1": "value1", 
    "key2": "value2"
  }, 
......
}
>>> print(r.json())  #返回的是一个Python字典
{... 'form': {'key1': 'value1', 'key2': 'value2'}, ......}
向URL post一个字符串,自动编码为data

>>> string = "skdkheh990"  #post一个字符串,自动编码为data
>>> r = requests.post(url="http://httpbin.org/post",data = string)
>>> print(r.text)        #返回json
{
   ......
  "data": "skdkheh990",  #post字符串自动存在data字段下
  "form": {}, 
  ......
  }
>>> r.json()             #json()方法返回一个Python字典
{...... 'form': {}, 'url': 'http://httpbin.org/post', 'data': 'skdkheh990',......}
Requests库的put方法:
和post方法一样,只不过会把原有的数据覆盖掉的

Requests库主要方法解析(7个)

r = requests.request(method,url,**kwargs),request方法是基础方法

method:请求方式

  • r = requests.request('GET',url,**kwargs)
  • r = requests.request('POST',url,**kwargs)
  • r = requests.request('PUT',url,**kwargs)
  • r = requests.request('PATCH',url,**kwargs)
  • r = requests.request('HEAD',url,**kwargs)
  • r = requests.request('delete',url,**kwargs)
  • r = requests.request('OPTIONS',url,**kwargs) #向服务器获取到服务器和客户端一些打交道的参数与获取资源无直接相干

**kwargs:控制方为参数,都是可选项

1.params:是字典或字节序列,作为参数增加到URL中,是get方法中使用

>>> kv = {'wd':'unittest'}
>>> r = requests.get(url='http://www.baidu.com',params=kv)
>>> r.status_code
200
>>> r.url
'http://www.baidu.com/?wd=unittest'
2.data:是字典、字节序列或文件对象,作为Request的内容,向服务器提交资源时使用,post、put、patch、delete方法使用

>>> dic ={'key1':'value1','key2':'value2'}      #
>>> r = requests.post(url='http://httpbin.org/post',data=dic)#post提交到服务器不会显示在URL上
>>> body='stringafasdf'
>>> r = requests.post(url="http://httpbin.org/post",data=body)
3.json:是json格式的数据,作为Request的内容,提交内容是json串时,用到json=。。。,post等方法使用

>>> json1 ={'key1':'value1'}
>>> r = requests.post(url="http://httpbin.org/post",json=json1)
>>> print(r.text)
{
  "args": {}, 
  "data": "{\"key1\": \"value1\"}", 
  "files": {}, 
  "form": {}, 
  "json": {             #json=json1会把数据提交到服务器的json域中
    "key1": "value1"
  }, 
......
}
post 控制参数用到data、json的区别:

  1. 提交的数据是字典或键值对,使用data=XXX,则提交到服务器的form表单下;
  2. 提交的数据是字符串,使用data=XXX,则提交到服务器的data下;
  3. 提交的数据是json串时,使用json=XXX,则提交到服务器的json域;

4.headers:字典格式,用于定制http请求头

>>> header={'user-agent':'chrome/10'} #模拟浏览器向服务器发起请求就是用headers
>>> r = requests.request('POST',url="http://httpbin.org/post",headers=header)
5.cookies:是字典或cookieJar格式,Request中的cookie

6.auth :是元组类型,用于http认证功能

7.files:是字典类型,用于向服务器传输文件

>>> fs = {'file':open(r'E:\test.txt','rb')}
>>> r = requests.post(url="http://httpbin.org/post",files = fs,timeout=30)
>>> print(r.text)
{
  "args": {}, 
  "data": "", 
  "files": {        #向服务器提交的文件保存到服务器的files域中
    "file": "hahfhadfhadsfhhsdflahlowej[of567890987654567890987654345678"
  }, 
  "form": {}, 
......
}
8.timeout:设定超时时间,秒为单位
在设定时间内没有返回内容则返回一个timeout异常

9.proxies:是字典类型,可以为我们爬取网页设定访问代理服务器,可以增加登录认证

>>> pxs = {'http':'http"//user:[email protected].:1234','https':'https://10.10.10.1.4321'}#设置2个代理,一个是http访问时使用的代理,另一个是https访问时使用的代理
>>> r = requests.get(url='http://www.baidu.com',proxies=pxs)                           #可以隐藏用户爬去网页时原来的IP地址信息
10.allow_redirects:Ture/False,默认是Ture,用于允不允许URL进行重定向

11.stream:Ture/False,默认是Ture,用于对获取的内容是否立即下载

12.verify:Ture/False,默认是Ture,用于验证SSL证书开关

13.cert:保存本地SSL证书路径的字段

网络爬虫中的Robots协议

网络爬虫的尺寸

  • 小规模,数据量小,爬取速度不明感,爬取网页:requests库(90%)
  • 中规模,数据量规模较大,爬取速度敏感,爬取网站、系列网站:Scrapy库
  • 大规模,搜索引擎,爬取速度关键,爬取全网:定制开发

网络爬虫的限制

网站是通过根据请求头中user-agent字段是否是浏览器来判断是否是爬虫爬取数据,通过headers={...}更改用户代理为浏览器就可以正常爬取数据了。

来源审查:判断user-agent进行限制

                  检查来访http协议头的user-agent域,只响应浏览器或友好爬虫的访问

发布公告:Robots协议(网络爬虫排除标准)

http://www.jd.com/robots.txt 京东网终的robots协议

robots协议是指:放在网站根目录下,用来告知爬虫哪些可以爬取哪些不能爬取数据。

爬取例子:

import requests
def getJd(url):
    try:
        header= {'user-agent':'Mozilla/5.0'}    #定制请求头,用户代理
        r = requests.get(url,headers=header,timeout=30)
        r.raise_for_status()     #不是200则爬出httperror
        r.encoding = r.apparent_encoding
        print(r.request.headers) #请求头
        print(r.headers)         #响应头
        return r.text[:1000]
    except:
        return "异常了!"
if __name__ == "__main__":
    url = "https://item.jd.com/4435312.html"
    print(getJd(url))
j记录学习--python网络爬虫与信息提取_第5张图片

图片爬取:

import requests
#爬取网上的图片、视频、动画等http://xxx.jpg/x.mp4/xxx.mp3
def gePicture(url,path):
    try:
        r = requests.get(url)
        r.raise_for_status()
    except:
        return '出错了'
    else:
        #with打开一个文件,然后把爬取到的内容(2进制)写进这个文件中,r.content表示返回内容的2进制形式
        with open(path,'wb') as f:
            #将返回的二进制图片数据写进文件中
            f.write(r.content)
        f.close()
if __name__ == "__main__":
    url = "http://xxx.mpv"
    path = r'D:\screenshot\girl1.jpg'
    gePicture(url,path)

图片爬取2:

import requests
import os
url = "https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1489415348&di=9d1919ad385f0b4afed19f83f1887e75&src=http://img.caixin.com/2014-03-24/1395633851186812.jpg"
root  = "D://screen//"
path = root + url.split('/2014-03-24/')[-1]
try:
    #文件夹不存在则新建一个
    if not os.path.exists(root):
        os.mkdir(root)
    #文件不存在则保存,存在则打印‘已存在’
    if not os.path.exists(path):
        r = requests.get(url)
        with open(path,'wb') as f :
            f.write(r.content)
            f.close()
            print('文件保存成功!')
    else:
        print('文件已经存在!')
except:
    print('爬取失败')

我们平时看到的人机交互方式,如:图像、文本框、点击按钮等等,浏览器向后台服务器提交请求的时候其实都是以链接的形式来提交的,只要通过浏览器的解析知道提交给后台的URL的形式那就可以用request库模拟浏览器向服务器做正式的提交。向网站提交数据时可以通过这种方式。

eg:在百度搜索框搜索内容其实是:http://www.baidu.com?wd='unittest'方式提交的

网络上任何内容都有一个URL,任何内容都是用URL定义的,都可以通过URL对网络上内容进行操作。

网络爬虫之规则-》Beautiful Soup库

Beautiful Soup的介绍和安装

Beautiful Soup库可以将HTML、XML进行解析并且提取其中的相关信息,可以将提供给它的任何格式进行爬取并且可以进行树形解析。

Beautiful Soup库的安装

pip install beautifulsoup4

j记录学习--python网络爬虫与信息提取_第6张图片

安装成功后使用Beautiful Soup库,先import进:

from bs4 import BeautifulSoup     (从bs4导进BeautifulSoup这个类)

soup = BeautifulSoup('

data

','html.parser') 

参数解析:

 

data

:需要BeautifulSoup解析的xml或html代码

  html.parser:对xml或html解析的解析器

使用例子:

>>> import requests
>>> r = requests.get(url='http://192.168.0.107/html5/girl.html')
>>> r.encoding = r.apparent_encoding
>>> demo = r.text                                 #通过requests库获取API页面
>>> from bs4 import BeautifulSoup                 #从bs4导进BeautifulSoup类
>>> soup = BeautifulSoup(demo,'html.parser')      #html.paeser是解析demo的解析器(对demo进行html的解析)
>>> print(soup.prettify())                        #调用prettify()方法,下面则是成功解析了。


 
  
   
    四大美女
   ...... ...... ...... ......
 </body>
</html></code></pre> 
  <p></p> 
  <h3>Beautiful Soup库的基本元素</h3> Beautiful Soup库是解析xml和html的功能库。html、xml大都是一对一对的标签构成,所以Beautiful Soup库是解析、遍历、维护“标签树”的功能库,只要提供的是标签类型Beautiful Soup库都可以进行很好的解析。 
  <p></p> 
  <p><p id='pp'>...</p>:标签Tag</p> 
  <p>p:标签的name</p> 
  <p>属性:成对存在</p> 
  <p><strong>Beauti Soup库的导入</strong></p> 
  <p>from bs4 import BeautifulSoup</p> 
  <p>import bs4</p> 
  <p>html文档 == 标签树 == BeautifulSoup类   可以认为三者是等价的</p> 
  <p></p> 
  <pre><code class="language-python">>>>from bs4 import BeautifulSoup
>>>soup = BeautifulSoup('<html>data</html>','html.parser')
>>>soup1=BeautifulSoup(open(r'D:\demo.html'),'html.parser')</code></pre> 
  <strong>简单来说一个BeautifulSoup类对应一个html文档的全部内容。如上面的soup、soup1都对应一个html文档。</strong> 
  <br> 
  <p></p> 
  <p style="text-align:center;"><strong>Beautiful Soup库的解析器</strong></p> 
  <p><a href="http://img.e-com-net.com/image/info8/4247ea5cc66d4b11ab23baa1e0c78a4c.jpg" target="_blank"><img alt="j记录学习--python网络爬虫与信息提取_第7张图片" src="http://img.e-com-net.com/image/info8/4247ea5cc66d4b11ab23baa1e0c78a4c.jpg" width="650" height="229" style="border:1px solid black;"></a><br></p> 
  <p>一般使用html.parser解析器就行了。</p> 
  <h4>Beautiful Soup类的基本元素</h4> 
  <p></p> 
  <ul> 
   <li>Tag<span> </span>标签,最基本的信息组织单元,分别用<></>开头结尾,通过<strong>soup.<标签></strong>获得</li> 
   <li>Name            标签名字,通过<strong><标签>.name</strong>获取到</li> 
   <li>Attribute         标签的属性,字典形式,通过<strong><标签>.attrs</strong>获取</li> 
   <li>Navigablestring 标签对之间的字符串,通过<strong><标签>.string</strong>获取</li> 
   <li>Comment   标签内字符串的注释部分,一种特殊的Comment类型</li> 
  </ul> 
  <p></p> 
  <p><strong>通过  soup.标签名  可获得该标签信息</strong><br> 当存在多个一样的标签时,默认返回第一个标签的信息。<br></p> 
  <p></p> 
  <pre><code class="language-python">>>> soup=BeautifulSoup(demo,'html.parser')
>>> soup.title
<title>四大美女
>>> soup.a
通过  soup.标签.name  可获取标签的名字,以字符串形式返回

>>> soup.a.name        #获取a标签的名字
'a'
>>> soup.a.parent.name #获取a标签的上级标签名字
'p'
>>> soup.a.parent.parent.name #获取a标签的上上级标签的名字
'hr'
通过   soup.标签.attrs  可获得标签的属性,以字典形式返回
>>> soup.a.attrs      #获取a标签的属性,字典返回
{'href': 'http://www.baidu.com', 'target': '_blank'}
>>> tag = soup.a.parent
>>> tag.name         #获取p标签的属性,字典返回
'p'
>>> tag.attrs
{'align': 'center'}

因为返回的是字典可以采用字典的方法对其进行信息的提取。

>>> for i,j in soup.a.attrs.items(): #for循环遍历字典
	print(i,j)
href http://www.baidu.com
target _blank
>>> soup.a.attrs['href']             #获取某个key的value值
'http://www.baidu.com'
>>> soup.a.attrs.keys()              #获取字典所有的keys
dict_keys(['href', 'target']) 
>>> soup.a.attrs.values()            #获取字典所有values
dict_values(['http://www.baidu.com', '_blank'])
通过  soup.标签.string  可以获取标签之间的文本,返回字符串
>>> soup.title.string   #获取title表之间的文本
'四大美女'
>>> soup.a.string       #获取a标签之间的文本,没有返回空
>>> soup.a
html中注释部分通过soup.html或soup不能显示出来,要查看html的注释怎么办?
>>> newsoup = BeautifulSoup('

这是段落

','html.parser') >>> newsoup.b.string '这是注释!' >>> newsoup.p.string '这是段落' >>> type(newsoup.b.string) >>> type(newsoup.p.string)

基于bs4库的HTML的遍历方法

HTML的基本格式

树形结构:

j记录学习--python网络爬虫与信息提取_第8张图片

标签树的下行遍历

属性及说明  返回的是列表

  • .contents      子节点的列表,将标签所有儿子节点存入列表===》返回列表类型
  • .children       子节点的迭代类型,与.content类似,用于循环遍历儿子节点
  • .descendants  子孙节点的迭代类型,包含所有子孙节点,用于循环遍历 ===》迭代类型,只能用在for循环中

也就是说contents和children只获得当前节点的下一层的节点信息,而descendants可以获得一个节点后续的所有节点信息用于遍历

>>> soup.head


四大美女

>>> soup.head.contents                #获取head标签下的儿子节点
['\n', 
四大美女
]
>>> len(soup.body.contents)          #通过len函数获取body标签的儿子节点个数
3
>>> for i in soup.body.children:     #遍历body标签的儿子节点
	print(i)
>>> for i in soup.body.descendants:  #遍历body标签所有的儿子、子孙节点
	print(i)

上行遍历

属性及说明

  • .parent      当前节点的父亲标签
  • .parents    节点先辈标签的迭代类型,用于循环遍历先辈节点。包含当前节点的直接父亲、先辈父亲。

>>> soup.a.parents

>>> for i in soup.a.parents:
	if i is None:              #对a标签的所有先辈标签进行打印
		print(i)
	else:
		print(i.name)          	
p
hr
body
html
[document]

平行遍历

平行遍历必须是发生在同一个父亲节点下的各节点间。

属性及说明

  • .next_sibling              返回按照html文本顺序的下一个平行节点标签
  • previous_sibling      返回按照html文本顺序的上一个平行节点标签
  • .next_siblings           迭代类型,返回按照html文本顺序的后续所有平行节点标签
  • .previous_siblings   迭代类型,返回按照html文本顺序的前续所有平行节点标签

迭代类型是需要通过循环遍历一个个打印出来的

在标签树中树形结构采用的是标签来组织,但标签之间的字符串也构成了标签的节点,也就是任何节点它的平行节点或儿子几点是存在string类型的所以不能一致认为它们都是标签。

>>> soup.p.next_sibling.next_sibling   #p标签的下一个再下一个平行节点

girl四大美女

>>> soup.p.previous_sibling #p标签的上一个平行节点 '\n' >>> soup.p.previous_sibling.previous_sibling #p标签的上一个再上一个的平行节点

基于bs4库的HTML格式化和编码

prettify()方法可以格式化html代码

>>> print(soup.a.prettify())

 新闻
bs4库将任何读入的html文件或字符串都转换成“utf-8”编码。
Python3系列默认支持编码是utf-8,所以中文时不需要进行Unicode转码。

信息标记的三种形式

XML

...  有内容时用一对标签表达

               空元素时缩写形式,没内容

JSON

 js对面向对象语言的表达形式,有类型的键值对。

对js等编程语言,可以把json直接当成编程语言的一部分,Python的或需要通过json库的dump()和load()方法字典和json的转换。

YAML

无类型的键值对,通过缩进表达所属关系,和Python很像。

通过-号表达并列关系

name:
-liyue

-liyu

-test

|表示整块数据,#注释

key:value

key:#comment   注释

-value1

-value2

key:

subkey:subvalue

信息提取的一般方法

融合方法:结合形式解析与搜索方法,提取关键信息。

例子:提取html中所有的链接

1.搜索到所有的a标签

2.解析a标签格式,提取href后的链接内容

>>> import requests
>>> r = requests.get(url='http://www.baidu.com',timeout=30)
>>> r.status_code
200
>>> r.encoding=r.apparent_encoding
>>> demo = r.text
>>> from bs4 import BeautifulSoup
>>> soup = BeautifulSoup(demo,'html.parser')
>>> for i in soup.find_all('a'):           #find_all()方法获取页面中所有的a标签
	print(i.get('href'))		   #get()方法获取url链接
	
http://news.baidu.com
http://www.hao123.com
http://map.baidu.com
http://v.baidu.com
http://tieba.baidu.com

基于bs4库的HTML内容查找方法

BeautifulSoup库提供了一个方法

find_all(name,attrs,recursive,string,**kwargs)

参数都是精确搜索,需要利用正则表达式模糊索索

import  re

re.compilt()

这个方法可以在soup的变量中查找信息,返回的是列表类型,存储查找的结果

name:对标签名称的检索字符串

>>> for i in soup.find_all(True):  #true则返回所有标签
	print(i.name)	
html
head
meta
meta
meta
link
title
body
>>> soup.find_all(['a','b'])      #返回a、b标签,同一个列表中
返回以b开头的标签:需要利用正则表达式库 re
>>> for i in soup.find_all(re.compile('b')):   #返回以b开头的标签,模糊匹配
	print(i.name)
	
body
attrs:对标签属性值的检索字符串,可以标注属性检索,属性值需要属性名称='属性值'
>>> soup.find_all('p',target='_blank')     #查找p标签,而且属性值是_blank的
[] 
>>> soup.find_all(id='su')          #查找id属性值是su的标签
[]
>>> soup.find_all(re.compile('link'))  #查找标签包含link的标签
[百度一下,你就知道]
>>> soup.find_all(id=re.compile('link'))  #查找属性名为id,值为包含link的标签
[]
recursive:是否对子孙全部检索,默认是True
string:<>.....对标签中的string检索

>>> soup.find_all('a',recursive=False) #将recursive设为False则只在儿子节点检索
[]
>>> soup.find_all(string='登录')       #string检索
['登录']  
>>> soup.find_all(string='关于')       #字符串检索,必须精确的检索,通过正则表达式可以模糊索索
[]
>>> soup.find_all(string=re.compile('关于'))  #通过正则表达式和find_all()函数可以很有效的通过字符串检索查该html或xml页面所有待该页面的该字段
['关于百度']
find_all()函数的简写形式:

soup()====soup.find_all()

()====.find_all()
j记录学习--python网络爬虫与信息提取_第9张图片

(定向爬虫、)网络爬虫实例

需求:

输入:需要爬取的URL

输出:需要爬取到的信息,并按一定排序输出

方法:requests、BeautifulSoup

定向爬虫:是指仅对指定的URL进行爬取,不扩展爬取

这类爬虫只能获取静态页面信息,对于js动态生成的页面信息再说。通过源代码可以判断是否是js动态生成。

程序设计思路:

1.从网页上获取网页内容---requests

2.提取网页内容的有效信息,并把信息放进合适的数据结构中,这样可以将信息变成我们代码的一部分

3.利用数据结构输出其中的信息,并达到我们所要的数据结果

程序结构设计:

1.获取网页内容

getHTMLText()

2.获取网络内容信息并存储到合适的数据结构中,列表

fillUnivList()

3.利用数据结构展示并输出结果

printUnivList()

实现代码:

import requests
from bs4 import BeautifulSoup
import bs4

#获取html页面
def getHTMLText(url):
    try:
        r = requests.get(url,timeout=30)
        r.raise_for_status()
        r.encoding = r.apparent_encoding
        return r.text
    except:
        return ""

#将html页面放到list列表中
def fillUnivList(ulist,html):
    soup = BeautifulSoup(html,'html.parser')
    # 查找tbody标签然后,下行遍历,只遍历儿子节点(tr)。tr标签是行,这里是一所大学的信息。
    for tr in soup.find("tbody").children:
        #tbody的儿子节点可能有tr标签和字符串类型(字符串类型也是节点),这里所有信息都在tr标签中,这里需要过滤掉非标签类型的信息
        #这里需要import bs4,检测循环遍历tr的类型,若不是bs4库定义的标签类型则过滤掉
        if isinstance(tr,bs4.element.Tag):
            #对tr标签中的td标签做查询,存为列表类型tds
	    #tds = tr.find_all("td")
            tds = tr('td') #find_all()的简写
            #将排名、大学名称、大学排分加进ulist列表中
            ulist.append([tds[0].string,tds[1].string,tds[3].string])

#将列表信息打印出来,num表示将html页面的多少个学校打印出来
def printUnivList(ulist,num):
    #.format()方法格式化输出
    #表头的打印
    print("{:^10}\t{:^6}\t{:^10}".format("排名","学校名称","总分"))
    #大学信息打印,格式和表头一致
    for i in range(num):
        u = ulist[i]
        print("{:^10}\t{:^6}\t{:^10}".format(u[0],u[1],u[2]))
    print("Suc" + str(num))
#主函数
def main():
    #将大学信息放进ulist列表中
    ulist = []
    url = "http://www.zuihaodaxue.cn/zuihaodaxuepaiming2016.html"
    #调用3个步骤对应的函数
    #将URL转换为html
    html = getHTMLText(url)
    #将html信息提取后放在uinfo的变量中
    fillUnivList(ulist,html)
    #打印大学信息
    printUnivList(ulist,25) #这里选取20所学校信息
main()

j记录学习--python网络爬虫与信息提取_第10张图片

中文输出对齐问题

j记录学习--python网络爬虫与信息提取_第11张图片

.format输出中文字符和西文字符

中文输出对齐问题在输出是中英文混合的情况下都存在。

解决方案:

当中文字符宽度不够时,系统默认采用西文字符填充。只要采用中文字符空格填充而不用西文空格填充,中文对齐问题就很很好解决了。

采用中文字符的空格填充采用chr(12288)表示一个中文空格。

#将列表信息打印出来
def printUnivList(ulist,num):
    tplt = "{0:^10}\t{1:{3}^10}\t{2:^10}"
    print(tplt.format("排名","学校名称","总分",chr(12288)))
    for i in range(num):
        u = ulist[i]
        print(tplt.format(u[0],u[1],u[2],chr(12288)))
    print("Suc" + str(num))
j记录学习--python网络爬虫与信息提取_第12张图片

网络爬虫之实战

正则表达式是什么

  • 正则表达式是用来简洁表达一组字符串的表达式。
  • 正则表达式是通用的字符串表达框架。
  • 正则表达式是简洁表达一组字符串的表达式。
  • 正则表达式是针对字符串表达“简洁”和“特征”思想的工具。
  • 正则表达式可以用来判断某字符串的特征归属。

另外正则表达式在文本处理中十分常用:

  • 正则表达式用来表达文本类型的特征如,病毒、入侵等。
  • 正则表达式可以表达一组字符串,用于查找替换。
  • 正则表达式可以匹配字符串的全部或一部分。

正则表达式的语法

正则表达式是由字符和操作符构成的。

正则表达式的常用操作符

j记录学习--python网络爬虫与信息提取_第13张图片

j记录学习--python网络爬虫与信息提取_第14张图片

常规例子:

P(Y|YT|YTH|YTHO)?N    "PN","PYN","PYTN","PYTHN","PYTHON"
PYTHON+               "PYTHON","PYTHONN","PYTHONNN..."
PY[TH]ON              "PYTON","PYHON"
PY[^TH]?ON            "PYON","PYAON","PYXON"
PY{:3}N               "PN","PYN","PYYN","PYYYN"
经典例子:

^[A-Za-z]+$          由26个字母组成的字符串
^[A-Za-z0-9]+$       由26个字母和数字组成的字符串
^-?\d+$              整数形式的字符串
^[0-9]*[1-9][0-9]*$  正整数形式的字符串
[1-9]\d{5}           中国境内邮政编码,6位
[\u4e00-\u9fa5]      匹配中文字符  #采用utf8编码约定中文字符取值范围
\d{3}-\d{8}|\d{4}-\d{7}  国内电话号码,010-68913536  11位啊
由字符串来构造正则表达式:
匹配IP地址的正则表达式,(IP地址分4段,每段是0-255)
精确的写法:

0-99:        [1-9]?\d

100-199:1\d{2}

200-249:2[0-4]\d

250-255:25[0-5]

ip地址:

(([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5]).){3}([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5])

Re库的基本使用

re库是Python的的标准库,主要用于字符串匹配。

调用方式:

import   re

正则表达式的表示类型

re库采用raw  string类型,也就是原生字符串类型。

原生字符串类型是指:斜杠\  不会转意。因为正则表达式中操作符有些是和斜杠一起构成的,使用原生字符串就可以防止转意了。

在字符串前加一个小写字母r就可以了。

如:

r"[1-9]?\d"

所以:

当正则表达式中包含转意符号时,使用原生字符串。

re库的功能函数

j记录学习--python网络爬虫与信息提取_第15张图片

函数的具体说明:

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

作用:

在一个字符串中搜索匹配正则表达式的第一个位置,返回match对象。

参数说明:

  • pattern:正则表达式的字符串或原生字符串
  • string:需要和这个正则表达式匹配的字符串
  • flags:正则表达式使用时的控制标记

flags:正则表达式使用时的控制标记

  • re.I     忽略正则表示是的大小写,[a-z]可以匹配大写
  • re.M  正则表达式中的^操作符能够将给定的字符串的每行当做匹配的开始
  • re.S   正则表达式中的.点操作符能够匹配所有的字符,默认匹配除换行符外的所有字符

例子:

>>> import re   #导入re库
>>> match = re.search(r'[1-9]\d{5}','GHF345326 GHF525300')   #匹配邮政编码,返回的是一个match对象
>>> if match:
	print(match.group(0))
	
345326
re.match(pattern,string,flags=0)
作用:

从一个字符串的开始位置起匹配正则表达式,返回match对象。

参数:

同search

例子:

>>> import re
>>> match1 = re.match(r'[1-9]\d{5}','GHF535343')
>>> if match1:
	print(match1.group(0))
#match()方法是从字符串的起始位置开始匹配,开始字符串不是,所有match对象为空。
>>> match1.group(0)   #要对返回的match对象进行打印时,需要判断match对象是否为空,不为空才能调用group(0)方法。
Traceback (most recent call last):
  File "", line 1, in 
    match1.group(0)
AttributeError: 'NoneType' object has no attribute 'group'
>>> match2 = re.match(r'[1-9]\d{5}','525300 GHT')
>>> if match2:
	match2.group(0)

'525300'
re.findall(pattern,string,flags=0)
作用:

搜索字符串,以列表的形式返回所有能够匹配的子串。

参数同search

例子:

>>> import re
>>> ls =re.findall(r'[1-9]\d{5}','234323gg GHT838476ff535243')
>>> ls
['234323', '838476', '535243']
re.split(patttern,string,maxsplit=0,flags=0)
作用:

将一个字符串按照正则表达式匹配的结果进行分割,返回列表类型。

pattren\string\flags和search、match、findall方法一样,

maxsplit:最大分割数,剩余部分作为最后一个元素输出

例子:

>>> import  re
>>> ls = re.split(r'[1-9]\d{5}','FG123456JJJ213456 234567SDF')
>>> ls
['FG', 'JJJ', ' ', 'SDF']    #返回一个列表
>>> ls1 = re.split(r'[1-9]\d{5}','FJD324538HJH879045',maxsplit=1)   #最大分割是1,所有只分割1个,剩下的原路返回
>>> ls1
['FJD', 'HJH879045']
>>> 
re.finditer(pattern,string,flags=0)
作用:

搜索字符串,返回一个匹配结果的迭代类型,每个迭代元素都是match对象。

参数和search、match、findall方法一样

例子:

>>> import re
>>> srting = 'ere123456kij234567pl345435'
>>> ls = re.finditer(r'[1-9]\d{5}',srting)
>>> type(ls)

>>> for i in ls:
	if i :
		print(i.group(0))	
123456
234567
345435
re.sub(pattern,repl,string,count=0,flags=0)
作用:

在一个字符串中替换所有匹配正则表达式的子串,返回被替换后的字符串。

参数pattern、string、flags和search、match、findall的参数一样。

repl:替换匹配字符串的字符串

count:匹配的最大替换次数

例子:

>>> import re
>>> re.sub(r'[1-9]\d{5}','repl111','GHT212123GTY345673',count=2)
'GHTrepl111GTYrepl111'
>>> re.sub(r'[1-9]\d{5}','+test+','GTH787878GHU898789')
'GTH+test+GHU+test+'
>>> re.sub(r'[1-9]\d{5}','+test+','GTH787878GHU898789',count=1)
'GTH+test+GHU898789'
>>> 
j记录学习--python网络爬虫与信息提取_第16张图片

以上通过.调用的方法使用re的方法是函数式用法:一次性操作

正则表达式还有另外一种方法:

面向对象用法:编译后多次操作

>>> import re
>>> pat = re.compile(r'[1-9]\d{5}')
>>> rst = pat.search('GHT 525343')
>>> rst
<_sre.SRE_Match object; span=(4, 10), match='525343'>
>>> if rst:
	print(rst.group(0))
	
525343
先使用compile()编译成正则表达式,之后再调用search、match、findall、split、sub、finditer方法。

没经过compile的字符串只是正则表达式的一种表现形式,经过compile后的才是正则表达式。

优点:一次编译可以多次使用该正则表达式进行匹配。

pat = compile(pattern,flags=0)

作用:

将一个正则表达式编译成一个正则表达式对象。

参数说明:

pattern:正则表达式字符串或原生字符串;

flags:正则表达式使用时的控制标记;

re库的match对象

re的search()、match()、finditer()返回的是一个match对象,search、match只返回匹配到的第一个字符串,需要返回全部匹配的字符串使用finditer,for循环全部打印出来。

match对象是:一次匹配的结果,它包含了很多匹配的相关信息。

match对象的属性

  • .string   待匹配的的文本
  • .re          匹配时使用的pattern对象(正则表达式)
  • .pos       正则表达是搜索文本的开始位置
  • .endpos  正则表达式搜索文本的结束位置
>>> import re	
>>> i = re.search(r'[1-9]\d{5}','GTR343435')
>>> if i :
	print(i.group(0))
	
343435 
>>> i.string       #待匹配的文本
'GTR343435'
>>> i.re           #正则表达式
re.compile('[1-9]\\d{5}')
>>> i.pos          #搜索文本的开始位置
0
>>> i.endpos       #搜索文本的结束位置
9
match对象的方法
  • .group(0)      获得匹配后的字符串
  • .start()           匹配字符串在原字符串的开始位置
  • .end()            匹配字符串在原字符串的结束位置
  • .span()          返回(.start(),.end())元组结构

>>> i.start()  #匹配字符串在原字符串的开始位置
3
>>> i.end()    #匹配字符串在原字符串的结束位置
9
>>> i.span()   #返回一个元组,包括匹配字符串在原字符串的开始位置和结束位置
(3, 9)
>>> i.group(0)  #返回匹配后的字符串
'343435'

正则表达式的贪婪匹配和最小匹配

re库默认采用贪婪匹配的方式,也就是返回匹配的最长项,如:

>>> import re
>>> i = re.search(r'py.*n','pyanbncndnfngn')
>>> if i:
	i.group(0)
'pyanbncndnfngn'    #默认采用贪婪匹配,返回最长的
如何输出最小匹配呢?
只要在*的后面增加一个?问号:

>>> ii = re.match(r'py.*?n','pyanbncndnfngn')
>>> if ii :
	ii.group(0)
	
'pyan'
最小匹配操作符:
j记录学习--python网络爬虫与信息提取_第17张图片

当有操作符可以匹配不同长度时,我们都可以在操作符后面增加一个问号?来获取最小匹配。

什么时候采用BeautifulSoup库?

什么时候采用re库?

什么样的网站适合定向爬虫实现爬取?数据写在html页面中,可以通过定位到元素,一般的静态页面,但那种有js生成的页面,源代码没有显示页面,不适合定向爬虫。

淘宝定向爬虫实例

从众多的文本信息中提取我们需要的信息,使用正则表达式是最好的。

当不使用BeautifulSoup库进行信息提取,通过搜索的方式提取信息,使用re库是最合适的。

新知识点:

  • 使用r.encoding = "utf-8" 比使用r.encoding = r.apparent_encoding节约时间
  • 字符串中引入双引号,需要在前面加反斜杠,r'\"view_price\"\:\"[\d\.]*\"'
  • eval()函数能够将字符串最外层双引号或单引号去掉
  • for 循环遍历range()是从0开始的,for i in plt:也可以的,但是下面也要再次遍历tlt、重复了。使用range(len(list)),获得列表的每个元素的序号,再通过下标可以得到列表元素了。
  • continue语句只是结束本次循环,而不会终止循环的执行。break语句则是终止整个循环过程。
  •  {}定义槽函数,{:4}第一个位置长度为4,中间8,最后是16。参考代码。
  • 字符串的.format()函数使用

import requests
import re
import time
#获取html页面
def getHTMLText(url):
    try:
        r = requests.get(url,timeout = 30)
        r.raise_for_status()
        r.encoding = "utf-8"  #可以节约时间
        return r.text
    except:
        return ""
#对获取的每一个页面进行解析,ilt是结果的列表类型
def parsePage(ilt,html):
    try:
        #"view_price":"149.00"
        #反斜杠\表示引入双引号,获取价格信息保存到plt中
        plt = re.findall(r'\"view_price\"\:\"[\d\.]*\"',html)
        #"raw_title":"2017春季新款双肩包女韩版时尚pu背包流苏子母包百搭学院风书包"
        #*?是最小匹配
        tlt = re.findall(r'\"raw_title\"\:\".*?\"',html)
        #获取了商品的价格和信息,下面对这两个信息关联起来,保存到我们要输出的变量中
        #plt\tlt列表长度一样,元素位置一一对应的,同一个位置的元素是同一个商品的
        for i in range(len(plt)):
            #字符串分割获取商品的价格,eval()函数能够将字符串的最外层的双引号或单引号去掉
            price = eval(plt[i].split(':')[1])
            #获得商品名称
            title = eval(tlt[i].split(":")[1])
            #将信息需要输出的列表中
            ilt.append([price,title])
    except:
        print("")
#将解析后的信息输出
def printGoodsList(ilt):
    #先设计一个打印模板tplt,希望打印什么格式,
    # {}定义槽函数,{:4}第一个位置长度为4,中间8,最后是16
    tplt = "{:4}\t{:8}\t{:16}"
    #打印输出信息的表头
    print(tplt.format("序号","价格","商品名称"))
    #定义一个输出信息计数器,商品序号
    count = 0
    #对所有的信息进行输出显示
    for i in ilt:
        count +=1        #序号,价格,名称
        print(tplt.format(count,i[0],i[1]))
#定义主函数,记录整个程序运行的过程
def main():
    start_time = time.time()
    #搜索关键词goods
    goods = "书包"
    #设定向下一页爬取的深度,爬取页数depth
    depth = 2
    #爬取的URL
    start_url = "https://s.taobao.com/search?q=" + goods
    #定义变量infoList 表示输出结果
    infoList = []
    #因为每一个页面URL不同,需要对每一个页面进行单独访问和处理
    for i in range(depth):
        try:  #使用try多获取页面进行异常判断,如果某页面解析出问题,可以跳过该页面,往下继续,不会造成出现解析错误,程序停止
            #对每一个页面的URL链接进行设计,因为淘宝每个页面显示44个商品
            url = start_url + '&s=' + str(44*i)
            html = getHTMLText(url)  #获取页面内容
            parsePage(infoList,html) #对获取的页面进行处理
        except:
            #continue语句只是结束本次循环,而不会终止循环的执行。break语句则是终止整个循环过程
            continue
    #将获取的页面信息输出
    printGoodsList(infoList)
    end_time = time.time()
    print(end_time-start_time)
    print(round(end_time-start_time))
main()
j记录学习--python网络爬虫与信息提取_第18张图片 j记录学习--python网络爬虫与信息提取_第19张图片

股票数据爬取实例

采用技术路线:

BeautifulSoup + re + requests + traceback(方便调试)

注意的知识点:


share_reptile.py

import requests
import re
import traceback
from bs4 import BeautifulSoup

#获得URL对应的页面
def getHTMLText(url):
    try:
        r = requests.get(url,timeout=30)
        r.raise_for_status()
        r.encoding = r.apparent_encoding
        return r.text
    except:
        return ""
#获得股票的信息列表,lst是保存所有股票的列表,存了所有股票信息;
# stockURL是获得股票列表的URL网站
def getStockList(lst,stockURL):
    html = getHTMLText(stockURL)               #获取东方财富网的html
    soup = BeautifulSoup(html,"html.parser")
    a = soup.find_all('a')                     #通过find_all()获得页面的a标签,a是一个列表
    for i in a :
        try:                       #代码中有很多不是股票链接的a标签的,解析可能出现错误,需要采用try...except...框架
            href = i.attrs["href"] #以s开头中间是h或z后面是6个数字的正则
            lst.append(re.findall(r'[s][hz]\d{6}',href)[0])#可以找到所有的股票URL
        except:
            continue
#获得每一只个股的股票信息,lst是保存所有股票的列表;stockURL是获得股票列表的URL网站;股票信息保存的文件路径
def getStockInfo(lst,stockURL,fpath):
    for i in lst:
        url = stockURL + i + ".html"
        html = getHTMLText(url)
        try:
            if html == "":
                continue
            infoDict = {}
            soup = BeautifulSoup(html,"html.parser")
            stockInfo = soup.find('div',attrs={'class':'stock-bets'})
            name = stockInfo.find_all(attrs={'class':'bets-name'})[0]
            infoDict.update({'股票名称':name.text.split()[0]})
            keyList = stockInfo.find_all('dt')
            valueList  = stockInfo.find_all('dd')
            for i in range(len(keyList)):
                key = keyList[i].text
                value = valueList[i].text
                infoDict[key] = value
            with open(fpath,'a',encoding='utf-8') as f:
                f.write(str(infoDict) + '\n')
        except:
            traceback.print_exc()
            continue

#主函数
def main():
    stock_list_url = 'http://quote.eastmoney.com/stocklist.html' #获得股票列表
    stock_info_url = 'https://gupiao.baidu.com/stock/'            #获取股票信息的链接的主题部分
    output_file = r'D:\gupaio_reptile.txt'                         #保存的路径
    slist = []                                                        #股票信息,列表
    getStockList(slist,stock_list_url)                                #获得股票列表
    getStockInfo(slist,stock_info_url,output_file)                    #获得每一只股票信息,保存到本地
main()

Scrapy爬虫

Scrapy爬虫框架的介绍、使用

Scrapy的安装

在command命令行输入:pip install scrapy














你可能感兴趣的:(python基础)