爬虫简介
什么是爬虫
爬虫是一种应用程序,用于从互联网中获取有价值的数据,从本质上来看,属于client客户端程序。
爬取的目标可以是整个互联网,也可以是单独的某一个服务器
互联网简介
互联网是由各种计算机设备,通过连接介质相互连接而组成的,其目的就是为了能在不同计算机之间传输数据,并且在互联网上有大量的数据是免费的。如果没有互联网,你只能拿着u盘过去拷贝。。
爬虫的原理
通常我们所谓的上网,其实本质就是用计算机通过网络去访问另一台计算机上的数据,而这些数据通常以网页的形式存在于服务器上,网页本质上就是一个文本文件,要想得到有价值的数据,第一步就是要得到这些网页数据,这就需要分析浏览器与服务器之间到底是如何通讯的,然后模拟浏览器来与服务器通讯,从而获取数据。当然爬虫不仅限于爬取网页,还可爬取移动设备常用的json数据等等,以及其他格式的二进制数据等等。
爬虫的价值
互联网中最宝贵的就是数据了,例如淘宝的商品数据,链家的房源信息,拉钩的招聘信息等等,这些数据就像一座矿山,爬虫就像是挖矿的工具,掌握了爬虫技术,你就成了矿山老板,各网站都在为你免费提供数据。
爬虫的爬取过程
需要借助网络编程,底层网络协议
分析请求
web页面(chrome)
原生软件(抓包工具如Charles)
模拟请求
第三方requests请求库 高度封装了HTTP
内置http请求库urllib
postman 接口测试工具 用于原生软件的爬虫程序
paw(mac)接口测试工具,可自动生成请求代码
获取响应数据
浏览器中的开发者工具
请求库的返回对象
解析数据
re模块
BeautifulSoup(封装了常用的正则表达式)
存储
文件,mysql,redis,MongoDB等
非关系型数据库(MongoDB,redis)一般用在高并发爬虫
HTTP请求分析
首先要明确的是:爬虫的核心原理就是模拟浏览器发送HTTP协议来获取服务器上的数据,那么要想服务器接受你的请求,则必须将自己的请求伪装的足够像,这就需要我们去分析浏览器是如何发送的HTTP请求
其次:HTTP协议是基于请求响应模型的,客户端发送请求到服务器,服务器接受请求,处理后返回响应数据,需要关注的重点在于请求数据,只有服务器认为合格合法的请求才会得到服务器的响应。
利用chrome开发者工具来分析请求
chrome浏览器提供强大的开发者工具我们可以利用它来查看浏览器与服务器的整个通讯过程
上图划出了常用的几个操作,如清空请求,保留日志等等,另一个常用操作就是清空cookie信息
打开chrome的设置页面搜索cookie就可以找到清空按钮。
请求流程分析
请求地址
浏览器发送的请求URL地址
请求方法
get 中文需要URL编码,参数跟在地址后面
post 参数放在body中
请求头
cookie,需要登录成功才能访问的页面就需要传递cookie,否则则不需要cookie
user-agent,用户代理,验证客户端的类型
referer,引用页面,判断是从哪个页面点击过来的
请求体
只在post请求时需要关注,通常post请求参数都放在请求体中,例如登录时的用户名和密码
响应头
location:重定向的目标地址,仅 在状态码为3XX时出现,需要考虑重定向时的方法,参数等。。,浏览器会自动重定向,request模块也会。
set-cookie:服务器返回的cookie信息,在访问一些隐私页面是需要带上cookie
响应体
服务器返回的数据,可能以下几种类型
HTML格式的静态页面 需要解析获取需要的数据
json格式的结构化数据 直接就是纯粹的数据
二进制数据(图片视频等) 通过文件操作直接写入文件
爬取梨视频
request模块的使用
get请求
请求参数
1.参数拼接时中文需要URLEncode,可以使用urllib中的编码函数来完成
from urllib.parse import urlencode
import requests
params = {"wd":"美女"}
# 必须以字典类型传入需要编码的数据 得到拼接收的参数字符串
res = urlencode(params,encoding="utf-8")
url = "https://www.baidu.com/s"
url = url+"?"+res # 拼接参数
response = requests.get(url)
with open("test.html","wb") as f:
f.write(response.content)
2.也可以直接将请求参数传给get的函数的params参数,get方法会自动完成编码
import requests
# 参数字典
params = {"wd":"美女"}
url = "https://www.baidu.com/s"
# 直接传给params参数
response = requests.get(url,params=params)
with open("test.html","wb") as f:
f.write(response.content)
上述的代码无法请求完成的数据 因为百度服务器有限制,必须制定用户代理,user-agent为浏览器才行
添加请求头
import requests
params = {"wd":"美女"}
url = "https://www.baidu.com/s"
headers = {
"user-agent" : 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Mobile Safari/537.36'
}
response = requests.get(url,params=params,headers=headers)
with open("test.html","wb") as f:
cookies
可以在headers中直接添加键值对也可以单独使用get方法的cookies参数
url = "https://github.com/yangyuanhu"
headers = {
"User-Agent" : 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Mobile Safari/537.36',
"Cookie":"_octo=GH1.1.1155792533.1532919557; _ga=GA1.2.1847433334.1534242542; has_recent_activity=1; user_session=Z5AQmC_Wv4wvM-_Nc3Bj0PQn6nITSonDcPkw4GZ1g0jFIqbe; __Host-user_session_same_site=Z5AQmC_Wv4wvM-_Nc3Bj0PQn6nITSonDcPkw4GZ1g0jFIqbe; logged_in=yes; dotcom_user=yangyuanhu; tz=Asia%2FShanghai; _gat=1; _gh_sess=eERwWHk1NVBjOEhIRmxzckcrcWlpVCtUM2hWL0prNlIyNXdTdC9ab0FSZWR5MEZlYW5OVGNoTEdJQUN0K0xkNXVmTEVpK2RRU1VZTUF6NkJHM1JMejNtaVhReXg2M3Vsb0JvcG8vNDNENjNBcXVvcFE4Tmp4SFhpQ3Z3S2ZZZEIwTGNkVW5BR01qVHlCNEFqTnZFcllhN3NwT1VJblZYWElLOGowN3ZaZVpZTFVHQlFtZkh3K1hnclBWalB1Mk1XLS1wNjFjMlBxUHljU2paK2RXU3M5eDB3PT0%3D--8cdf657174d2acac7cc0459da667accc8a2d0f5e",
"Referer": "https://github.com/"
}
response = requests.get(url,headers=headers)
with open("test.html","wb") as f:
f.write(response.content)
# 使用cookies参数 注意需要拆分为单独的键值对
#response = requests.get(url,headers=headers,cookies={"_octo":"GH1.1.1155792533.1532919557"})
requests模块自动处理cookies
POST请求
post请求不同之处仅在于参数提交通过post方法的data参数来指定:
模拟登录流程并爬取github私有页面
import requests
import re
user_agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36"
# 创建一个会话
session = requests.session()
# 1.请求登录页面
res = session.get("https://github.com/login",headers={
"Referer": "https://github.com/",
"User-Agent": user_agent
})
print(res.status_code)
token = re.search('name="authenticity_token" value="(.*?)"',res.text).group(1)
print(token)
# 2.发送登录请求,带上cookie,带上请求体,带上token
res2 = session.post("https://github.com/session",
headers = {
"Referer": "https://github.com/login",
"User-Agent": user_agent},
data={
"commit": "Sign in",
"utf8": "✓",
"authenticity_token": token,
"login": "oldboyedujerry",
"password": "123654asdAsd"},
allow_redirects = True)
print(res2.status_code)
# 访问个人主页
res3 = session.get("https://github.com/settings/profile",
headers={
"User-Agent": user_agent,
"Referer": "https://github.com/login"
})
print(res3.status_code)
with open("tt.html","wt") as f:
f.write(res3.text)
# 判断是否登录成功
print("oldboyedujerry" in res3.text)
注意通常登录时都会带token,需要现货区token才能进行post登录
session的使用
使用会话来完成请求,会话会自动帮我们保存和提交cookie
user_agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36"
# 创建一个会话
session = requests.session()
# 1.请求登录页面
res = session.get("https://github.com/login",headers={
"Referer": "https://github.com/",
"User-Agent": user_agent
})
print(res.status_code)
token = re.search('name="authenticity_token" value="(.*?)"',res.text).group(1)
print(token)
# 2.发送登录请求,带上cookie,带上请求体
res2 = session.post("https://github.com/session",
headers = {
"Referer": "https://github.com/login",
"User-Agent": user_agent},
data={
"commit": "Sign in",
"utf8": "✓",
"authenticity_token": token,
"login": "oldboyedujerry",
"password": "123654asdAsd"},
allow_redirects = False
)
print(res2.status_code)
# 访问个人主页
res3 = session.get("https://github.com/settings/profile",
headers={
"User-Agent": user_agent,
"Referer": "https://github.com/login"
})
print(res3.status_code)
with open("tt.html","wt") as f:
f.write(res3.text)
# 判断是否登录成功
print("oldboyedujerry" in res3.text)
response属性
import requests
respone=requests.get('http://www.jianshu.com')
# respone属性
print(respone.text) # 获取解码后的内容 html
print(respone.content) # 获取二进制字节数据
print(respone.json()) # 对响应进行json反序列化
print(respone.status_code)
print(respone.headers)
print(respone.cookies)
print(respone.cookies.get_dict())
print(respone.url)
print(respone.history) # 重定向历史
print(respone.encoding) # 响应体编码方式
#关闭:上下文管理
from contextlib import closing
with closing(requests.get('xxx',stream=True)) as response:
for line in response.iter_content():
pass
指定响应体编码
import requests
response=requests.get('http://www.autohome.com/news')
response.encoding = "GBK"
print(response.text)
print(response.encoding)
分段获取数据
# 当响应体数据太大的时候一次性获取所有可能造成内存溢出
# 可以使用iter_content 分段获取数据,
# chunk_size指定每次获取的长度,
# decode_unicode 是否进行解码 通常为false
import requests
respone = requests.get("https://www.qq.com")
with open("tt.html","wt") as f:
for i in respone.iter_content(chunk_size=1024,decode_unicode=False):
f.write(i)
获取原始套接字响应数据
r = requests.get('https://api.github.com/events', stream=True)
# <requests.packages.urllib3.response.HTTPResponse object at 0x101194810>
print(r.raw.read(10))
SSL Cert Verification
#证书验证(大部分网站都是https)
import requests
respone=requests.get('https://www.12306.cn') #如果是ssl请求,首先检查证书是否合法,不合法则报错,程序终端
#改进1:去掉报错,但是会报警告
import requests
respone=requests.get('https://www.12306.cn',verify=False) #不验证证书,报警告,返回200
print(respone.status_code)
#改进2:去掉报错,并且去掉警报信息
import requests
from requests.packages import urllib3
urllib3.disable_warnings() #关闭警告
respone=requests.get('https://www.12306.cn',verify=False)
print(respone.status_code)
#改进3:加上证书
#很多网站都是https,但是不用证书也可以访问,大多数情况都是可以携带也可以不携带证书
#知乎\百度等都是可带可不带
#有硬性要求的,则必须带,比如对于定向的用户,拿到证书后才有权限访问某个特定网站
import requests
respone=requests.get('https://www.12306.cn',
cert=('/path/server.crt',
'/path/key'))
print(respone.status_code)
使用代理
#官网链接: http://docs.python-requests.org/en/master/user/advanced/#proxies
#代理设置:先发送请求给代理,然后由代理帮忙发送(封ip是常见的事情)
import requests
proxies={
'http':'http://112.98.126.100:8118',#带用户名密码的代理,@符号前是用户名与密码
'https':'https://124.42.68.152:90',
}
respone=requests.get('https://www.baidu.com',
proxies=proxies)
print(respone.status_code)
#支持socks代理,安装:pip install requests[socks]
import requests
proxies = {
'http': 'socks5://user:pass@host:port',
'https': 'socks5://user:pass@host:port'
}
respone=requests.get('https://www.12306.cn',
proxies=proxies)
print(respone.status_code)
超时设置
#超时设置
#两种超时:float or tuple
#timeout=0.1 #代表接收数据的超时时间
#timeout=(0.1,0.2)#0.1代表链接超时 0.2代表接收数据的超时时间
import requests
respone=requests.get('https://www.baidu.com',
timeout=0.0001)
捕获异常
#异常处理
import requests
from requests.exceptions import * #可以查看requests.exceptions获取异常类型
try:
r=requests.get('http://www.baidu.com',timeout=0.00001)
except ReadTimeout:
print('===:')
# except ConnectionError: # 链接失败
# print('-----')
# except Timeout: # 超时
# print('aaaaa')
except RequestException: # 其他异常
print('Error')
上传文件
import requests
files={'file':open('a.jpg','rb')} # value为文件对象
respone=requests.post('http://httpbin.org/post',files=files)
print(respone.status_code)