爬虫:网络爬虫机器人,从互联网自动抓取数据的程序
理论上:通过浏览器看到的数据,我们一般都是可以获取到的
爬虫的作用:
- 搜索引擎
2.商品比价(慧慧购物助手)
3.知乎的数据分析平台(知乎专栏,数据冰山)
网页的三大特征:
- 每一个网页都有一个唯一的url(统一资源定位符),来进行定位
- 网页都是通过HTMl(超文本)文本展示的
- 所有的网页都是通过http<超文本传输协议>(HTTPS)协议来传输的
爬虫的流程
- 分析网站,得到目标url
- 根据url,发起请求,获取页面的HTML的源码
- 从页面源码中提取数据
a. 提取到目标欧数据,做数据的赛选和持久化存储
b. 从页面中提取到新的url地址,继续执行第二部操作 - 爬虫结束:所有的目标url都提取完毕,并且得到了数据,再也没有其他请求任务了,这是意味着爬虫结束
通用爬虫抓取网页的流程:
- 选取一部分的url作为种子url,将这些url放入到带爬取得任务队列里
- 从带爬取得任务队列中取出url,发起请求,将获取到的页面源码存储到本地,并将已经爬取过的url,放入已爬取队列中
- 从已爬取url的响应结果中,分析提取其他的url地址,继续添加到带爬取队列中,之后就是不断的循环,查到所有的url都提取完毕
通用爬虫的缺点:
1)必须遵守roobot协议:就是一个规范,告诉搜索引擎,哪些目录下的资源允许爬虫,哪些目录下的资源不允许爬取
‘User-agent’:该项值用来表示是哪家的搜索引擎
‘allow’:允许被爬取的url
‘disallow’:不允许被爬取的url
2)搜索引擎返回的都是网页,并且返回90%的都是无用的数据
3)不能够跟剧不同的用户需求或者检索结果返回不同的结果
4)通过爬虫对于对媒体的文件不能够获取
OSI七层协议
从上往下:
应用层:为用户的应用程序提供网络服务的(http,https,ftp。。。。。)
表示层:负责端到端的数据信息可以被另一个主机所理解和识别,并且按照一定的格式将信息传递到会话层
会话层:管理主机之间的会话进程,负责建立,管理,和终止会话进程
传输层:进行数据传输(TCP UDP)
网络层: 路由器
数据链路层:网桥 交换机
物理层 : 网线 网卡 集线器 中继器
常见的请求状态码
200 :请求成功
301 : 永久重定向
302 : 临时重定向
401 : 未授权
403 : 服务器拒绝访问
404 : 页面丢失
405 : 请求方式不对
408 : 请求超时
500 : 服务器错误
503 : 服务器不可用
发起请求:
会携带请求头:
’USer-Agent‘:模拟浏览器请求
‘Cookies’:存储在浏览器里,使用cookie表明身份
‘Refere’:说明当前请求是从哪个页面发起
使用urllib发起请求
#目标url
url = 'http://www.baidu.com/'
# request.urlopen():使用urlopen方法模拟浏览器发起请求
"""
url, 请求的目标url地址
data=None,默认情况为None,表示发起的是一个get请求,不为None,则发起的是一个post请求
timeout=,设置请求的超时时间
cafile=None, 设置证书
capath=None, 设置证书路径
cadefault=False, 是否要使用默认证书(默认为False)
context=None:是一个ssl值,表示忽略ssl认证
"""
#是一个ssl值,表示忽略ssl认证(如果请求出现了ssl证书认证错误,
# 我们就需要设置ssl._create_unverified_context(),忽略证书认证)
content = ssl._create_unverified_context()
response = request.urlopen(url,timeout=10,content=content)
#从response响应结果中获取参数
#状态码
code = response.status
print(code)
#获取页面源码的二进制数据
b_html = response.read()
print(type(b_html),len(b_html))
#获取响应的响应头部(Response Headers)
res_headers = response.getheaders()
print(res_headers)
#获取响应头中指定参数的值
cookie_data = response.getheader('Set-Cookie')
print(cookie_data)
#reason返回一个响应结果的原因
reason = response.reason
print(reason)
#将获取到的二进制数据,转换为字符串decode
str_html = b_html.decode('utf-8')
print(type(str_html))
with open('b_baidu.page.html','w') as file:
# file.write(b_html)
file.write(str_html)
#如果请求要携带请求头
#需要先构建一个request对象
"""
url:发起请求的url地址
data=None, 默认情况为None,表示发起的是一个get请求,不为None,则发起的是一个post请求
headers={},设置请求头(headers对应的数据类型是一个字典)
origin_req_host=None, (指定发起请求的域)
unverifiable=False,忽略SSL认证
method=None:指定发起请求的方式
"""
req_header = {
'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
}
req = request.Request(url,headers=req_header)
#根据构建的req请求对象发起请求
response = request.urlopen(req)
response.status
正则
. :表示匹配除了换行符之外的任意字符
\ :转义字符
[a-z] : 匹配a-z里面的任意一个字符
\d: 匹配数字 -> [0-9]
\D: 匹配非数字 [^\d]
\s: 匹配空白字符(空格,\n,\t...)
\S: 匹配非空白字符
\w: 匹配单词字符 [A-Za-z0-9_]
\W: 匹配非单子字符
^:匹配以...开头
$:匹配以....结尾
():分组
|:或
多字符匹配
*:匹配*前面的字符任意次数
+ : 匹配+号前面的字符至少1次
?: 匹配?前面的字符0次或1次
{m}:匹配{m}前面的字符m次
{m,n}:匹配{m,n}前面的字符m~n次
非贪婪匹配
*?
+?
??
{m,n}?
列子
import re
#把正则表达式构建为一个pattern对象
sub_str = 'abcdefabcd'
pattern = re.compile('b')
#从字符串的起始位置开始匹配,开头就必须符合正则规则,
# 如果匹配到结果了返回结果,如果匹配不到返回None,单次匹配
result = re.match(pattern,sub_str)
print(type(result))
if result:
print(result.group())
#在整个字符串中进行匹配,同样是单次匹配,匹配到结果立即返回
#匹配不到则返回None
result = re.search(pattern,sub_str)
print(result.group())
# 再整个字符串中进行匹配,匹配出所有符合正则规则的结果,
# 以列表的形式返回
result = re.findall(pattern,sub_str)
print(result)
#再整个字符串中进行匹配,匹配出所有符合正则规则的结果,
#但是返回的是一个迭代器
result = re.finditer(pattern,sub_str)
# <class 'callable_iterator'>
print(type(result))
for note in result:
#<class '_sre.SRE_Match'>
print(type(note))
print(note.group())
#替换re.sub()
url = 'http://www.baidu.com/s?kw=aaa&pn=20'
# pattern, \正则规则
# repl, \要替换的字符串
# string,原始字符串
pattern = re.compile('pn=\d+')
result = re.sub(pattern,'pn=30',url)
print(result)
#分割re.split()
pattern = re.compile('[=:&]')
#pattern, string
result = re.split(pattern,url)
print(result)
sub_html = """
<div class="threadlist_title pull_left j_th_tit ">
<a rel="noreferrer" href="/p/5982749825" title="来聊" target="_blank" class="j_th_tit ">来聊</a>
</div>
"""
#re.S让点可以匹配包括换行符的任意字符
pattern = re.compile(
'<div.*?class="threadlist_title pull_left j_th_tit ">'+
'.*?<a.*?href="(.*?)".*?</div>',re.S
)
result = re.findall(pattern,sub_html)
print(result)
cookies的使用
from urllib import request
# 目标url:
# https://www.douban.com/people/175417123/
url = 'https://www.douban.com/people/175417123/'
# 设置请求头
req_header = {
'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36',
'Cookie': 'bid=a5HEJxBlOLY; douban-fav-remind=1; ll="108288"; _vwo_uuid_v2=D4EC41D965B5FF84E814BF48AE34386A5|f6047e628a6acc98722e3100dfbc399c; __yadk_uid=KpfeK1cgib5IWMKWvG66MnJMbonYvDZa; _ga=GA1.2.36315712.1531837787; douban-profile-remind=1; push_doumail_num=0; push_noty_num=0; __utmv=30149280.17541; _gid=GA1.2.2070226630.1545227516; ps=y; _pk_ref.100001.8cb4=%5B%22%22%2C%22%22%2C1545292749%2C%22https%3A%2F%2Fwww.baidu.com%2Flink%3Furl%3DqpgHwc2FYGfOrGzt4yK3ZwwbraVm_oED80whnivpFaC3kA5IvGnUfQ9FSRZBOEVh%26wd%3D%26eqid%3D85ed620e000397aa000000065c1b4bc8%22%5D; _pk_ses.100001.8cb4=*; __utma=30149280.36315712.1531837787.1545227511.1545292752.48; __utmc=30149280; __utmz=30149280.1545292752.48.37.utmcsr=baidu|utmccn=(organic)|utmcmd=organic; __utmt=1; dbcl2="175417123:yY3DyEelGJE"; ck=FxtM; ap_v=0,6.0; _pk_id.100001.8cb4=7521abce5497fc2c.1531837784.39.1545292778.1545228853.; __utmb=30149280.7.10.1545292752',
}
req = request.Request(url=url,headers=req_header)
response = request.urlopen(req)
if response.status == 200:
with open('douban.html','w') as file:
file.write(response.read().decode('utf-8'))
cookiejar的使用
#使用cookiejar的目的:管理cookie,保存cookie值,
#一旦存储cookie之后,下一次发起请求的时候就会携带cookie
#cookie是保存在内存里面的,最后会进行垃圾回收
from urllib import request,parse
from http.cookiejar import CookieJar
#创建cookiejar对象,目的如上
cookie_jar = CookieJar()
#HTTPCookieProcessor创建handle处理器,管理cookiejar
handler = request.HTTPCookieProcessor(cookie_jar)
#自定义opener
opener = request.build_opener(handler)
#分析发现
# https://www.douban.com/accounts/login
# 没有验证码的情况
# source: index_nav
# form_email: 18518753265
# form_password: ljh12345678
#有验证码的情况
# source: index_nav
# form_email: 18518753265
# form_password: ljh12345678
# captcha-solution: blade
# captcha-id: 5IBtw5wm2riyrIrnV3utwUPt:en
url = 'https://www.douban.com/accounts/login'
form_data = {
'source': 'index_nav',
'form_email': '18518753265',
'form_password': 'ljh12345678',
'captcha-solution': 'noise',
'captcha-id': 'waNQIJD6TkMaF4M51PFg5kYh:en'
}
form_data = parse.urlencode(form_data).encode('utf-8')
#设置请求头
req_header = {
'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36',
}
#够建一个request对象
req = request.Request(url,headers=req_header,data=form_data)
#发起请求
response = opener.open(req)
#登录成功后访问个人主页,能够成功获取到个人主页信息,说明确实保存了cookie
#并且在一下次发起请求的时候携带了cookie
url = 'https://www.douban.com/people/175417123/'
req = request.Request(url,headers=req_header)
response = opener.open(req)
if response.status == 200:
with open('douban.html','w') as file:
file.write(response.read().decode('utf-8'))
urllib.error的使用
# urllib.error:在发起请求的过程中,可能会因为各种情况
# 导致请求出现异常,因而导致代码崩溃,所以我们悬疑处理这些异常的请求
from urllib import error,request
# error.URLError
def check_urlerror():
"""
1.没有网络
2.服务器连接失败
3.找不到指定服务器
:return:
"""
url = 'http://www.baidu.com/'
try:
response = request.urlopen(url,timeout=0.01)
print(response.status)
except error.URLError as err:
#[Errno -2] Name or service not known(未知服务器)
#timed out:请求超时
#[Errno -3] Temporary failure in name resolution(没网)
print(err.reason)
# check_urlerror()
# error.HTTPError是URLError的子类
def check_httperror():
url = 'https://www.qidian.com/all/nsacnscn.htm'
try:
response = request.urlopen(url)
print(response.status)
except error.HTTPError as err:
#HTTPError的三个属性
#状态码
print(err.code)
#返回错误的原因
print(err.reason)
#返回响应头
print(err.headers)
except error.URLError as err:
print(err.reason)
check_httperror()
urllib的parse模块的使用
#urllib的parse模块主要是实现url的解析,合并,编码,解码
from urllib import parse
#parse.urlparse实现了url的识别和分段
url = 'https://www.1712B.com/daxuesheng?name=zhangsan#123'
"""
url,:要解析和才分的url
scheme='':设置协议,只有在url没有协议的情况下才会生效
allow_fragments=True:是否忽略锚点,默认为True表示不忽略
"""
result = parse.urlparse(url)
"""
(scheme='https'(协议), netloc='www.1712B.com'(域),
path='/daxuesheng'(路径), params=''(可选参数),
query='name=zhangsan'(查询参数), fragment='123'(锚点))
"""
print(result)
#取出才分后的某一个参数
print(result.scheme)
#parse.urlunparse可以实现url的组合
data = [sub_str for sub_str in result]
print('-----',data)
full_url = parse.urlunparse(data)
print(full_url)
#parse.uurlrljoin需要传递一个基类url,根据基类将某一个不完整的url拼接完整
sub_url = '/p/123456'
base_url = 'https://www.1712B.com/daxuesheng?name=zhangsan#123'
full_url = parse.urljoin(base_url,sub_url)
print('urljoin',full_url)
#parse.urlencode将字典类型的参数,序列化为url的编码格式的字符串
parmars = {
'name':'张三',
'class':'1712B',
}
result = parse.urlencode(parmars)
print('urlencode',result)
#parse.parse_qs反序列化,将url编码格式的字符串,转为字典类型
result = parse.parse_qs(result)
print('parse_qs',result)
#parse.quote可以将中文字符,转为url编码格式
kw = '摸摸摸'
result = parse.quote(kw)
print('quote',result)
#将url编码进行解码
result = parse.unquote(result)
print('unquote',result)
# 最最常用的urljoin,urlencode两个方法
urllib_proxy的使用
# urllib下使用代理
# http/https代理
# 一定是一个高匿代理理
# 隐藏真实ip
from urllib import request
#自定义ProxyHandler的目的是为了设置代理,使用代理发起请求
#proxies:对应的是一个字典
# 代理有免费代理(西刺,快代理.....)
# 和收费代理 (西刺,快代理.....,阿布云....)
# proxies = {
# 'http':'118.187.58.34:53281',
# 'https':'124.235.180.121:80',
# }
#独享代理,需要账号密码做验证的
proxies = {
'http':'http://2295808193:6can7hyh@106.12.23.200:16818',
'https':'https://2295808193:6can7hyh@106.12.23.200:16818'
}
handler = request.ProxyHandler(proxies=proxies)
#自定义opener
opener = request.build_opener(handler)
#url地址
#https://httpbin.org/get
url = 'http://httpbin.org/get'
response = opener.open(url)
print(response.status)
print(response.read().decode('utf-8'))
requests模块
#pip3 install requests
#requests模块:是对urllib的封装,可以实现urllib的所有功能
#并且api调用更加简单方便
import requests
# url = 'http://www.baidu.com/'
url = 'http://www.sina.com'
# url, :要请求的目标url
# params:get请求后面要拼接的参数
"""
:param method: 要发起的是什么类型的请求.
:param url: 要请求的目标url
:param params: get请求后面要拼接的参数
:param data: Dictionary, post请求的表单数据
:param json: 传递json数据跟上面的data效果类似
:param headers: (optional) Dictionary 请求头
:param cookies: (optional) Dict or CookieJar object (设置cookies信息模拟用户请求)
:param files: 上传文件
:param auth: 网站需要验证的信息(账号和密码)
:param timeout: 设置请求的超时时间
:param allow_redirects: bool,是否允许重定向
:param proxies: (optional) Dictionary (设置代理)
:param verify: Defaults to ``True``.(忽略证书认证,默认为True表示不忽略)
"""
req_header = {
'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36',
}
parmars = {
'wd':'豆瓣'
}
# response = requests.get(url,params=parmars,headers=req_header)
response = requests.get(url,headers=req_header)
response.encoding='utf-8'
#从响应结果中获取的信息
#(这里得到的是解码后的字符串)
html = response.text
"""
#如果使用response.text出现了乱码
方式一
#response.content.decode('')
方式二
response.encoding=''设置编码类型
"""
#获取bytes类型的数据
b_html = response.content
#获取状态码
code = response.status_code
#获取响应头
response_headers = response.headers
#请求头
req_headers = response.request.headers
#获取当前请求的url地址
current_url = response.url
#response.json():可以将json字符串转为python数据类型
print(code)
print(html)
resquests_post请求
import requests
#url, 目标url
# data=None,:post请求要上传的表单数据
url = 'https://www.lagou.com/jobs/positionAjax.json?needAddtionalResult=false'
form_data = {
'first': 'true',
'pn': 1,
'kd': 'python',
}
#设置请求头
req_header = {
'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36',
'Referer': 'https://www.lagou.com/jobs/list_python?city=%E5%85%A8%E5%9B%BD&cl=false&fromSearch=true&labelWords=&suginput=',
}
response = requests.post(url,data=form_data,headers=req_header)
print(response.status_code)
print(response.text)
#可以吧将返回的json字符串转为python数据类型
data = response.json()
print(type(data))
requests_outh
#web客户端验证
import requests
#设置认证信息
auth = ('username','password')
url = 'http://192.168.1.110'
response = requests.get(url,auth=auth)
print(response.status_code)
requests下使用cookies
import requests
#分析发现
# https://www.douban.com/accounts/login
# 没有验证码的情况
# source: index_nav
# form_email: 18518753265
# form_password: ljh12345678
#有验证码的情况
# source: index_nav
# form_email: 18518753265
# form_password: ljh12345678
# captcha-solution: blade
# captcha-id: 5IBtw5wm2riyrIrnV3utwUPt:en
url = 'https://www.douban.com/accounts/login'
form_data = {
'source': 'index_nav',
'form_email': '18518753265',
'form_password': 'ljh12345678',
'captcha-solution': 'violent',
'captcha-id': 'AuKNJ1FIktyrmpljJ6WAzXo3:en'
}
#设置请求头
req_header = {
'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36',
}
#发起请求
response = requests.post(url,headers=req_header,data=form_data)
#使用response.cookies获取cookies信息
print('模拟登录后的cookies信息',response.cookies)
print(type(response.cookies))
print(response.headers)
with open('douban.html','w') as file:
file.write(response.text)
#requests.utils.cookiejar_from_dict():将字典转为cookiejar
#requests.utils.dict_from_cookiejar():将cookiejar转为字典
cookies_dict = requests.utils.dict_from_cookiejar(response.cookies)
print(cookies_dict)
#登录成功后访问个人主页,能够成功获取到个人主页信息,说明确实保存了cookie
#并且在一下次发起请求的时候携带了cookie
url = 'https://www.douban.com/people/175417123/'
#设置cookies参数,模拟用户发起请求
response = requests.get(url,headers=req_header,cookies=cookies_dict)
if response.status_code == 200:
with open('douban1.html','w') as file:
file.write(response.text)
使用requests模块设置代理
import requests
proxies = {
'http':'219.238.186.188:8118',
'https':'222.76.204.110:808',
'https':'https://username:password@ip:port',
'http':'http://username:password@ip:port'
}
url = 'https://httpbin.org/get'
response = requests.get(url,proxies=proxies,timeout=10)
print(response.text)
requests.session的使用
#requests.session():维持会话,可以让我们在跨请求时保存某些参数
import requests
#实例化session
session = requests.session()
#目标url
url = 'https://www.douban.com/accounts/login'
form_data = {
'source': 'index_nav',
'form_email': '18518753265',
'form_password': 'ljh12345678',
'captcha-solution': 'stamp',
'captcha-id': 'b3dssX515MsmNaklBX8uh5Ab:en'
}
#设置请求头
req_header = {
'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36',
}
#使用session发起请求
response = session.post(url,headers=req_header,data=form_data)
if response.status_code == 200:
#访问个人主页:
url = 'https://www.douban.com/people/175417123/'
response = session.get(url,headers = req_header)
if response.status_code == 200:
with open('douban3.html','w') as file:
file.write(response.text)
xpath的使用
-
xpath:可以在xml中查找信息,对xml文档中元素进行遍历和属性的提取
-
xml:被设计的目的是为了传输数据,结构和html非常相识,是一种标记语言
xpath常见的语法:
nodename 选取此节点的所有子节点
/ 从根节点开始查找
// 匹配节点,不考虑节点的位置
. 选取当前节点
.. 选取当前节点的父节点
a/@href 取标签的数据
a/text() 取标签的文本
a[@class="123"] 根据class属性寻找标签
a[@id="123"] 根据id属性寻找标签
a[@id="123"][last()] 取最后一个id为123的a标签
a[@id="123"][postion() < 2] 取id为123的前两个a标签
网友评论