Web Crawler
Capture 2 爬虫基础
2.1 HTTP基本原理
2.1.1 URI和URL
通过一个链接, 我们便可以从互联网上到这个资源,这就是URL/URI 。 URN只命名资源而不指定如何定位资源,比如um:isbn:0451450523指定了一本书的ISBN,可以唯一标识这本书,但是没有指定到哪里定位这本书,这就是URN。
2.1.2 超文本
网页的源代码HTML 就可以称作超文本。
2.1.3 HTTP和 HTTPS
https:基于ssl安全层的http
2.1.5 请求
- 请求方法 :常见的请求方法有两种:GET和POST。 · GET请求中的参数包含在URL里面,数据可以在URL中看到,而POST请求的URL不会包 含这些数据, · GET请求提交的数据最多只有1024字节,而POST方式没有限制。
- 请求的网址:URL
- 请求头:用来说明服务器要使用的附加信息,比较重要的信息有Cookie、Referer、User-Age等。
- 请求体:一般承载的内容是 POST请求中的表单数据,而对于GET请求,请求体则为空。
2.1.6 响应
由服务端返回给客户端,可以分为三部分:响应状态码、响应头和响应体;
1. 响应状态码
响应状态码表示服务器的响应状态:
- 响应头:响应头包含了服务器对请求的应答信息:如Content-Type(文档类型)、Server(包含服务器的信息)、Set-Cookie等
- 响应体:响应的正文数据都在响应体中,
2.2 网页基础
2.2.1 网页的组成——HTML、CSS和JavaScript。
- HTML 超文本标记语言。
- CSS 层叠样式表,为了让网页看起来更好看一些,
- JavaScripct 实现了一种实时、 动态、交互的页面功能。
2.2.2 网页的结构
A simple Example
<!-- simple_example.html -->
<!DOCTYPE html>
<html>
<head> <!-- 网页头(标签页)-->
<meta charset="UTF-8">
<title>This is a Demo</title>
</head>
<body> <!-- 网页体:网页正文中显示的内容 -->
<div id="container"> <!-- div 标签定义了网页中的区块,
它的id是 container, id的内容在网页中是唯一的,我们可以通过它来获取这个区块-->
<div class="wrapper"> <!--然后在此区块内又有一个div标签,
它的 class 为 wrapper, 经常与CSS配合使用-->
<h2 class="title">Hello World</h2> <!--h2代表一个二级标题-->
<p class="text">Hello, this is a paragraph.</p> </div> <!--p标签代表一个段落-->
</div>
</body>
</html>
2.2.3 节点树及节点间的关系
在HTML中, 所有标签定义的内容都是节点, 它们构成了1个HTML DOM树。
DOM:文档对象模型,是W3C(万维网联盟)的标准。W3C DOM标准被分为3个不同的部分: * 核心 DOM:针对任何结构化文档的标准模型。 * XML DOM:针对XML文档的标准模型。 * HTML DOM:针对HTML文档的标准模型。
2.2.4 选择器
在CSS中,我们使用 CSS选择器来定位节点。 上例中div节点的id为container,那么就可以表示为#container,#开头代表选择id, 其后紧跟id的名称. 另外, 如果我们想选择class为wrapper的节点,便可以使用.wrapper,这里以点(.)开头代表选择class,其后紧跟class的名称。 另外,还有一种选择方式,那就是根据标签名筛选,例如想选择二级标题,直接用h2即可。 这是最常用的3种表示,分别是根据id、class、标签名筛选.
2.3 爬虫的基本原理
2.3.1 爬虫概述
- 获取网页(源代码):最关键的部分就是构造一个请求并发送给服务器, 然后接收到响应并将其解析出来
- 提取信息:获取网页源代码后,接下来就是分析网页源代码;
- 保存数据
- 自动化程序
2.3.2 能抓怎样的数据
常规网页、JSON字符串、二进制数据, 如图片、 视频和音频等。
2.3.3 JavaScript渲染页面
得到的源代码实际和浏览器中看到的不一样,原始的 HTML 代码就是空壳,Be like
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>This is a Demo</title> </head>
<body>
<div id="container">
</div>
</body>
<script src="app.js"></script> </html>
2.4 会话和Cookies
2.4.1 静态网页和动态网页
文字、 图片等内容均通过写好的HTML代码来指定的页面叫作静态网页,可维护性差。
2.4.2 无状态HTTP
HTTP的无状态是指HTTP协议对事务处理是没有记忆能力的,意味着如果后续需要处理前面的信息,则必须重传。 因此需要会话和cookies 会话在服务器,来保存用户的会话信息,Cookies在客户端,浏览器在下次访间网页时会自动附带上 它发送给服务器,服务器通过识别Cookies并鉴定出是哪个用户,然后再判断用户是否是登录状态,然后返回对应的响应。
- 会话:在Web中,会话对象用来存储特定用户会话所需的属性及配置信息。
- Cookies:某些网站为了辨别用户身份、进行会话跟踪而存储在用户本地终端上的数据。
- 会话维持:当客户端第一次请求服务器时, 服务器会返回一个请求头带有Set-Cookie字段的响应给客户端,Cookies携带了会话ID信息,服务器检查该Cookies即可找到对应的会话是什么,所以,Cookies和会话需要客户端和服务器端的配合。
-
属性结构:Cookies有很多条目, 其中每个条目可以称为Cookie.
-
会话cookies和持久cookies :放在浏览器内存里,浏览器关闭即消失/保存到客户端的硬盘中 严格来说,只是由Cookie的Max Age或Expires字段决定了过期的时间。
2.5 代理的基本原理
借助某种方式来伪装我们的IP,让服务器识别不出是由我们本机发起的请求
2.5.1 基本原理
代理实际上指的就是代理服务器,代理网络用户去取得网络信息。
2.5.2 代理的作用
- 访问一些平时不能访问的站点。
- 访问一些单位或团体内部资源
- 提高访问速度
- 隐藏真实IP;
Capture 3 基本库的使用
3.1 使用urllib
Python 内置的 HTTP请求库 * request: 它是最基本的 HTTP 请求模块, 可以用来模拟发送请求。 * error: 异常处理模块 * parse:工具模块,提供url处理方法 * robotparser: 主要是用来识别网站的robots.txt文件
3.1.1 发送请求
- urlopen() :urllib.request模块
urlopen API urllib.request.urlopen(url, data=None, timeout=
import urllib.request response = urllib.request.urlopen("https://www.python.org") print(response.status) #状态码 print(response.getheaders()) #响应的头信息 print(response.getheader('Connection')) #
, cafile=None, capath=None, cadefault=False, context=None) - data 如果要添加该参数, 并且如果它是字节流编码格式的内容, 即 bytes 类型, 则需要通过 bytes()方法转化。 另外,如果传递了这个参数, 则它的请求方式就是POST方式。
我们传递的参数出现在了form字段中,这表明是模拟了表单提交的方式,以 POST方式传输数据。
-
timeout参数 timeout参数用于设置超时时间,单位为秒,如果不指定该参数, 就会使用全局默认时间。
-
Request 用法:
其他Request可选参数:data(bytes字节流类型),headers(添加请求头伪装浏览器),请求方法等import urllib.request request = urllib.request.Request('http://python.org') response = urllib.request.urlopen(request) print(response.read().decode('utf-8'))
也可以用add_header写from urllib import request, parse url = 'http://httpbin.org/post' headers = { 'User-Agent':'Mozilla/4.0(compatible;MSIE 5.5 ; Windows NT)', #模拟一个旧版本的 Internet Explorer(MSIE 5.5)和 Windows NT 操作系统。 'Host': 'Httpbin.org'# 指定目标网站的主机名 } dict = { 'name' : 'Germay' } data = bytes(parse.urlencode(dict),encoding='utf-8') #将字典转换成 URL 编码格式的字符串,编码后的字符串转换为字节对象 req = request.Request(url=url,data=data,headers=headers,method='POST') response = request.urlopen(req) print(response.read().decode('utf-8'))
-
高级用法 更强大的工具Handler,简而言之就是各种处理器。
- 验证:有些网站在打开时就会弹出提示框, 直接提示你输入用户名和密码, 验证成功后才能查看页面, 借助HTTPBasicAuthHandler就可以完成
- Cookies:
import http.cookiejar , urllib.request filename = 'cookie.txt' cookie = http.cookiejar.MozillaCookieJar(filename) handler = urllib.request.HTTPCookieProcessor(cookie) opener = urllib.request.build_opener(handler) response = opener.open('http://www.baidu.com') cookie.save(ignore_discard=True , ignore_expires=True) ``` 搞到了cookie之后 ```py import http.cookiejar , urllib.request cookie = http.cookiejar.LWPCookieJar() cookie.load('cookie.txt',ignore_discard=True,ignore_expires=True) handler = urllib.request.HTTPCookieProcessor(cookie) opener = urllib.request.build_opener(handler) responde = urllib.request.urlopen('http://www.baidu.com') print(responde.read().decode('utf-8')) ``` #### 3.1.2 处理异常 1. URLError ```py from urllib import request,error try: response = request.urlopen('http://www.baidu.com/111') except error.URLError as e: print(e.reason)
-
HTTPError URLError 的子类,专门用来处理 HTTP 请求错误,有3个属性 1.code: 返回 HTTP 状态码, 2.reason: 3.headers:返回请求头
比较好的处理异常写法from urllib import request,error try: response = request.urlopen('https://baidu.com') except error.HTTPError as e: print(e.reason,e.code,e.headers,sep = '\n') except error.URLError as e: print(e.reason) else: print('Request Successfully')#最后, 用 else 来处理正常的逻辑
有时候的错误为字符串,比如urlopen设置了timeout
3.1.3 解析链接
- urlparse() 实现 URL 的识别和分段 所以, 可以得出一个标准的链接格式,具体如下: scheme://netloc/path;params?query#fragment
- scheme :协议
- netloc:网络位置,包括域名、IP地址或端口
- path:服务器上的具体资源位置,通常是文件路径。
- params:用于传递给资源的可选参数
- query:包含查询参数,通常以键值对的形式表示,用 ? 开始,键值对之间以 & 分隔。
-
fragm:指向资源的片段或内部部分
-
urlunparse() 构造url
-
urlencode() :构造get请求
3.1.4分析Robots协议
- robotparser
3.2 使用requests
3.2.1 基本用法
-
import requests r = requests.get('http://www.baidu.com') print(type(r)) print(r.status_code) print(type(r.text)) print(r.text) print(r.cookies) print(r.headers) r = requests. post('http://httpbin.org/post') r = requests. put('http: I /httpbin.org/put') r = requests.delete('http://httpbin.org/delete') r = requests. head('http: I /httpbin.org/get') r = requests. options('http://httpbin.org/get')
-
Get请求
请求的链接自动被构建成了"url": "http://httpbin.org/get?name=Germay&age=22"
抓取页面
import requests
import re
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36'
}
r = requests.get('http://www.zhihu.com/explore',headers=headers)
pattern = re.compile('explore-feed.*?question_link.*?>(.*?)</a>',re.S)
titles = re.findall(pattern,r.text)
print(titles)
import requests
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36'
}
r = requests.get('http://github.com/favicon.ico',headers=headers)
with open('github.ico','wb') as file:
file.write(r.content)
- Post 请求
3.2.2 高级用法
-
文件上传
-
cookies
import requests
r = requests.get('http://www.baidu.com')
print(r.cookies)
for key,value in r.cookies.items():
print(key + '=' + value)
import requests
s = requests. Session()
s.get('http://httpbin.org/cookies/set/number/123456789')
r = s.get('http://httpbin.org/cookies')
print(r.text)
- SSL证书验证
- 代理设置
- 超时设置
- 身份认证
3.3 正则表达式
- match
可以这样单独提取
import re content = 'Hello 123 4567 World_This is a Regex Demo' print(len((content))) result = re.match('^Hello\s\d\d\d\s\d{4}\s\w{10}',content) print(result) print(result.group()) print(result.span())
- 通用匹配 .可以匹配任意字符(除换行符) ,*代表匹配前面的字符无限次
- 贪婪/非贪婪 贪婪 非贪婪
- 修饰符 re.S 使.能够识别换行符,re.I使match对大小写不敏感
-
转义匹配
-
search 因为 match方法在使用时需要考虑到开头的内容,这在做匹配时并不方便
import re html ='''<div id="songs-list") <h2 class="title">经典老歌</h2) <p class="introduction"> 经典老歌列表 </p> <ul id="list" class="list-group"> <li data-view="2">一路上有你</li> <li data-view="7"> <a href="/2.mp3" singer="任贤齐">沧海一声笑</a> </li> <li data-view="4" class="active"> <a href="/3.mp3" singer="齐秦">往事随风</a> </li> <li data-view="6"><a href="/4.mp3" singer="beyond">光辉岁月</a></li> <li data-view="5"><a href="/5.mp3" singer="陈慧琳">记事本</a></li> <li data-view="5"> <a href="/6.mp3"singer="邓丽君">但愿人长久</a> </li> </ul> </div> ''' result = re.search('<li.*?active.*?singer="(.*?)">(.*?)</a>',html,re.S) print(result.group(1),result.group(2))
-
findall
results = re.findall('<a.*?href="(.*?)".*?singer="(.*?)">(.*?)</a>',html,re.S)
print(results)
print(type(results))
for result in results:
print(result)
print(result[0],result[1],result[2])
-
sub 用sub去掉原字符串一些东西
第一个''表示去掉什么,第二个''表示替换成什么 -
compile
Capture 4 解析库的使用
4.1 使用XPath
-
XPath 概览 XML Path Language,在XML文档中查找信息的语言
-
自动修正
from lxml import etree text = ''' <div> <ul> <li class="item-1"><a href="link2.html">second item</a></li> <li class="item-inactive"><a href="link3.html">third item</a></l1> <li class="item-1"><a href="link4.html">fourth item</a></li> <li class="item-0"><a href="link5.html">fifth item</a> </ul> </div> ''' html = etree.HTML(text) result = etree.tostring(html) # 调用 tostring方法即可输出修正后的 HTML 代码 # 比如补全li节点标签 print(result.decode('utf-8')) #但是是字节形式,要转成utf-8
-
所有节点
-
子节点
-
父节点
-
属性匹配 选取class为item-1的li节点,
能知道匹配了2个 - 文本获取 text ()可以获取节点内部文本,来尝试获取前面li节点中的文本
-
属性获取 所有li 节点下所有a 节点的href 属性
属性匹配是中括号加属性名和值来限定某个属性,如[@href="link1.html"], 而此处的@href指的是获取节点的某个属性,二者需要做好区分。 -
属性多值匹配 某些节点的某个属性可能有多个值
from lxml import etree text ='''<li class="li li-first"><a href ="link.html">first item</a></li>'''#li 节点的class 属性有两个值li 和li-first html = etree.HTML(text) result = html.xpath('//li[@class="li li-first"]/a/text()') #result = html.xpath('//li[contains(@class,"li")]/a/text()') ,第一个参数传属性名称,第二个参数传属性值 print(result)
-
多属性匹配
-
按序选择
-
节点轴选择
from lxml import etree html = etree.parse('./test.html',etree.HTMLParser()) result = html.xpath('//li[1]/ancestor::*') result = html.xpath('//li[1]/ancestor::div') result = html.xpath('//li[1]/attribute::*')# attribute轴,可以获取所有属性值 result = html.xpath('//li[1]/child::a[@href="link1.html"]') #取href属性为link1.html直接子节点的a节点 result = html.xpath('//li[1]/descendant::*[2]') print(result)
4.2 Beautiful Soup
-
基本用法
html = ''' <html><head><title>The Dormouse's story</title></head> <body> <p class="title" name="dromouse"><b>The Dormouse's story</b></p> <p class="story">Once upon a time there were three little sisters; and their names were <a href="http://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>, <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and <a href="http://example.com/tillie" class="sister" id="link3" > Tillie</a>; and they lived at the bottom of a well.</p> <p class="story">...<Ip> ''' from bs4 import BeautifulSoup soup = BeautifulSoup(html,'lxml')#对于不标准的 HTML 字符串可以自动更正格式 print(soup.prettify()) #以标准的缩进格式输出 print(soup.title.string)
-
节点选择器
- 提取信息 (1)获取节点名称:name (2)获取属性:attrs (3)获取内容:string
- 嵌套选择
- 关联选择
(1)子节点和子孙节点
得到一个直接子节点的列表
html = ''' <html> <head> <title>The Dormouse's story</title> </head> <body> <p class="story"> Dnce upon a time there were three little sisters; and their names were <a href="http://example.com/elsie" class="sister" id="link1"> <span>Elsie</span> </a> <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a> and they lived at the bottom of a well. </p> <p class="story">... </p> ''' from bs4 import BeautifulSoup soup = BeautifulSoup(html,'lxml') print(soup.p.contents)
print(soup.p.children)#是一个生成器,也是遍历直接子节点
for i,child in enumerate(soup.p.children):#enumerate 用于在遍历可迭代对象时,为每个元素提供一个索引值
print(i,child)
(2)父节点、祖先节点类似,注意查询的是第一个节点的父/祖 父:parent 祖先parents(生成器)
(3)siblings
html = '''
<html>
<body>
<p class="story">
Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">
<span>Elsie</span>
</a>
Hello
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>
and
<a href="http://example.com/tillie" class="sister" id="link3"> Tillie</a>
and they lived at the bottom of a well.
</p>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,'lxml')
print('Next sibling',soup.a.next_sibling)
print('Pre sibling',soup.a.previous_sibling)
print('Next Siblings',list(enumerate(soup.a.next_siblings)))#生成器
print('Pre Siblings',list(enumerate(soup.a.previous_siblings)))
html = '''
<html>
<body>
<p class="story">
Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">Bob</a><a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>
</p>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,'lxml')
print(list(soup.a.parents)[0].attrs['class'])
html = '''
<div class="panel">
<div class="panel-heading">
<h4>Hello</h4>
</div>
<div class="panel-body">
<ul class="list" id="list-1">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>
<Ul class="list list-small" id="list-2">
<li class="element">Foo</li>
<li class="element">Bar</li>
</ul>
</div>
</div>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,'lxml')
print(soup.find_all(name='ul'))
print(type(soup.find_all(name='ul')[0]))#Tag
for ul in soup.find_all(name='ul'):
print(ul.find_all(name= 'li'))
for li in ul.find_all(name='li'):
print(li.string)
(3)text(string)
import re
html = '''
<div class="panel">
<div class="panel-body">
<a>Hello, this is a link</a>
<a>Hello, this is a link, too</a>
</div>
</div>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,'lxml')
print(soup.find_all(string=re.compile('link')))
>>>['Hello, this is a link', 'Hello, this is a link, too']
- find 返回第一个元素,findall返回所有的列表 还有诸如 find_parents() find_parent() find_next_siblings() find_previous_siblings() find_all_previous() ....
4.3 CSS 选择器
-
标签选择器 Step:
-
转换数据类型 selector = parsel.Selector(html) #字符串 ———>对象
-
css提取数据 p = selector.css('p') #提取p标签 .get():从提取到的对象中获取第一个元素(字符串) .getall():获取所有元素(列表)
-
类选择器
标签必须具有类属性 用.代表类属性 具有同类型的都会被提取 result = selector.css('.top').getall() 如果标签内有空格,要用.进行连接 空格表示子孙节点
-
ID选择器 标识符为#,id一般唯一
-
组合选择器 其实就是塞一大坨
Capture 6 Ajax数据爬取
这是因为requests 获取的都是原始的HTML 文档,而浏览器中的页面则是经过JavaScript 处理数据后生成的结果,这些数据的 来源有多种,可能是通过Ajax 加载的、可能是包含在HTML文档中的,也可能是经过JavaScript和特 定算法计算后生成的。 分析网页后台向接口发送的Ajax 请求,如果可以用requests 来模拟Ajax 请求
6.1什么是Ajax
利用JavaScript 在保证页面不被刷新、页面链接不改变的情况下与服务器交换数据并更新 部分网页的技术。 发送Ajax 请求到网页更新的这个过程可以简单分为以下3 步: 1. 发送请求 * 新建了XMLHttpRequest 对象 * 调用onreadystatechange 属性设置了监听 * 调用open()和send()方法向某个链接(也就是服务器)发送了请求 * 所以当服务器返回响应时, onreadystatechange 对应的方法便会被触发,然后在这个方法里面解析响应内容即可。
-
解析内容 利用xmlhttp 的responseText 属性便可取到响应内容(类似于Python 中利用requests 向服务器发起请求,然后得到响应的过程) 返回内容可能是HTML ,可能是JSON。只需要在方法中用JavaScript 进一步处理即可
-
渲染网页 JavaScr刷有改变网页内容的能力,解析完响应内容之后,就可以调用JavaScript 来针对解析完的 内容对网页进行下一步处理了。