14-通用爬虫模块-数据获取
爬虫基本概念
1. HTTP和HTTPS
HTTP:超文本传输协议,默认端口80
HTTPS:HTTP+SSL(安全套接字层),默认端口443
HTTPS比HTTP安全,但性能低。
2. url形式
形式:scheme://host[:port]/path/.../[?query-string][#anchor]
scheme:协议(例如http、https、ftp)
host:服务器的ip地址或者域名
port:服务器端口号(如果是走协议默认端口,如80 or 443)
path:访问资源的路径
query-string:参数,发送给http服务器的数据
anchor:锚(跳转到网页的指定锚点位置)
http://localhost:4000/filepart1/1.html
http://item.jd.com/119876.html/#product-detail
3. url请求形式
4. HTTP常见请求头
- Host:主机和端口号
- Connection:链接类型
- Upgrade-Insecure-Requests:升级为https请求
- User-Agent:浏览器名称
- Referer:页面跳转处
- Accept:传输文件类型
- Accept-Encoding:文件编解码格式
- Cookie
- x-Rquested-with:XMLHttpRequest(是Ajax异步请求)
5. 响应状态码
- 200:成功
- 302:临时转移至新的url
- 307:临时转移至新的url
- 404:not found
- 500:服务器内部错误
6. 爬虫的分类
- 通用爬虫:通常指搜索引擎的爬虫
-
聚焦爬虫:针对特定网站的爬虫
爬虫流程.png
7. 爬虫的概念
网络爬虫(又被称为网络蜘蛛,网络机器人),就是模拟客户端发送网络请求,接收请求响应,一种按照一定的规则,自动的抓取互联网信息的程序。
只要浏览器能做的事情,理论上,爬虫都能做。
8. 爬虫的更多用途
- 12306抢票
- 网站上的投票
- 短信轰炸
9. 通用搜索引擎的局限
- 通用搜索引擎里返回的网页里90%的内容都无用
- 图片、音频、视频多媒体的内容通用搜索引擎无能为力
- 不同的用户搜索的目的不完全相同,但是返回内容相同
10. ROBOTS协议
Robots协议:网站通过Robots协议告诉搜索引擎哪些页面可以抓取,哪些页面不能抓取。
https://www.taobao.com/robots.txt
11. 页面上的数据在哪里
- 当前url地址对应的响应中
- 其他的url地址对应的响应中
- 比如ajax请求中
- js生成的
- 部分数据在响应中
- 全部通过js生成
爬虫request库
1. requests使用入门
【文档地址】http://docs.python-requests.org/zh_CN/latest/index.html
requests和urlib的区别:
- requests的底层实现就是urlib
- requests在python2和python3中通用,方法完全一样
- requests简单易用
- requests能够自动帮助我们解压(gzip压缩等)网页内容
2. requests中解决编解码的方法
- response.content.decode()
- response.content.decode("gbk")
- response.text
3. response.text和response.content的区别
- response.text
- 类型:str
- 解码类型:根据HTTP头部对响应的编码作出有根据的推测,推测的文本编码
- 如何修改编码方式:response.encoding="gbk"
- response.content
- 类型:bytes
- 解码类型:没有指定
- 如何修改编码方式:response.content.decode("utf8")
4. 保存图片
import requests
r = requests.get("https://www.baidu.com/img/bd_logo1.png")
with open("a.png", "wb") as f:
f.write(r.content)
5. 发送简单的请求
response = requests.get("http://www.baidu.com")
response 的常用方法:
response.text
response.content
response.status_code
response.request.header
response.headers
6. 发送带header的请求
- headers的形式:字典
- headers= {"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"}
- 用法:response = requests.get(url, headers = headers)
7. 发送带参数的请求
- 例如:http://www.baidu.com?wd=python&c=b
- 参数形式:字典
- kw = {"wd":"长城"}
- 用法:requests.get(url,params=kw}
8. 贴吧爬虫
import requests
class TiebaSpider:
def __init__(self, tieba_name):
self.tieba_name = tieba_name
self.url_temp = "https://tieba.baidu.com/f?kw="+tieba_name+"&ie=utf-8&pn={}"
self.headers = {"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"}
def get_url_list(self): # 构造url列表
# url_list = []
# for i in range(1000):
# url_list.append(self.url_temp.format(i * 50))
# return url_list
return [self.url_temp.format(i*50) for i in range(1000)]
def parse_url(self, url): # 发送请求,获取响应
response = requests.get(url, headers=self.headers)
return response.content.decode()
def save_html(self, html_str,page_num): # 保存html字符串
file_path = "{}-第{}页.html".format(self.tieba_name, page_num)
with open(file_path, "w", encoding="utf-8") as f:
f.write(html_str)
def run(self): # 实现主要逻辑
# 1. 构造url列表
url_list = self.get_url_list()
# 2. 遍历,发送请求,获取请求
for url in url_list:
html_str = self.parse_url(url)
# 3. 保存
page_num = url_list.index(url) + 1 # 页码数
self.save_html(html_str, page_num)
if __name__ == '__main__':
tieba_spider = TiebaSpider("李毅")
tieba_spider.run()
9. 发送POST请求
- 登录注册(post比get安全)
- 需要传输大文本内容的时候(post请求对数据长度没有要求)
- 用法:response = requests.post("http://www.baidu.com/",data=data,headers=headers)
- data形式:字典
百度翻译爬虫
mport requests
import json
import sys
query_string = sys.argv[1]
headers = {"User-Agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1"}
post_data = {
"query": query_string,
"from": "zh",
"to": "en",
}
post_url = "http://fanyi.baodu.com/basetrans"
r = requests.post(post_url, data=post_data, headers=headers)
dict_rect = json.loads(r.content.decode())
ret = dict_rect["trans"][0]["dst"]
print("resulst is :", ret)
10. 使用代理
- 让服务器以为不是同一个客户端在请求
- 防止我们的真是地址被泄漏,防止被追究
- 用法:request.get("http://www.baidu.com",proxies)
- proxies:字典
proxies:{
"http":"http://12.34.56.78:9527",
"https":"https://12.34.56.78:9527"
}
11. 使用代理ip注意事项
-
准备一堆ip地址,组成ip池,随机使用一个ip地址来使用
-
如何随机选择代理ip
- {"ip":ip,"times":0}
- [{},{},{},{},...] 对这个ip池进行排序,按照使用次数排序
- 选择使用次数较少的10ip,从中随机选择一个
-
检测ip代理可用性
- 使用request添加超时参数,判断ip地址的质量
- 在线代理ip质量检测的网站
12. cookie和session区别
- cookie存储在客户的浏览器上,session数据放在服务器上
- cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗
- session会在一定时间保存在服务器上,当访问增多,会比较占用服务器的性能
- 单个cookie保存的数据不能超过4k,很多浏览器都限制一个站点最多保存20个cookie
13. 爬虫处理cookie和session
-
带上cookie和session的好处:能够请求到登录之后的页面
-
带上cookie和session的弊端:
- 一套cookie和session往往和一个用户对应
- 请求太快,请求次数过多,容易被服务器识别为爬虫
不要cookie的时候,尽量不要去使用cookie
但是为了获取登录之后的页面,我们必须发送带有cookie的请求
14. 处理cookie和session请求
requests提供了一个session的类,来实现客户端和服务端的会话保持。
使用方法:
1.实例化一个session对象
2.让session发送get或者post请求
session = requests.session()
response = session.get(url,headers)
15. 请求登录之后网站的思路
- 实例化session
- 先使用session发送请求,登录网站,把cookie保存在session中
- 再使用session请求登录之后才能访问的网站,session能够自动的携带登录成功时保存在其中的cookie,进行请求
16. demo
方式1:实例化session,使用session发送post请求,在使用他获取登录后的页面
import requests
session = requests.session()
post_url = "http://192.168.0.223:8080/redmine/login"
post_data = {"username": "zyx", "password": "30********35"}
headers = {"User-Agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1"}
# 使用session发送post请求,cookie保存在其中
session.post(post_url, data=post_data, headers=headers)
# 在使用session进行请求登录之后才能访问的地址
r = session.get("http://192.168.0.223:8080/redmine/issues?utf8=%E2%9C%93&set_filter=1&f%5B%5D=status_id&op%5Bstatus_id%5D=%21&v%5Bstatus_id%5D%5B%5D=3&f%5B%5D=assigned_to_id&op%5Bassigned_to_id%5D=%3D&v%5Bassigned_to_id%5D%5B%5D=me&f%5B%5D=created_on&op%5Bcreated_on%5D=%3E%3D&v%5Bcreated_on%5D%5B%5D=2019-03-01&f%5B%5D=&c%5B%5D=project&c%5B%5D=tracker&c%5B%5D=status&c%5B%5D=priority&c%5B%5D=subject&c%5B%5D=assigned_to&c%5B%5D=updated_on&c%5B%5D=parent&c%5B%5D=start_date&c%5B%5D=due_date&c%5B%5D=done_ratio&group_by=")
# 保存页面
with open("renren.html", "w", encoding="utf-8") as f:
f.write(r.content.decode())
方式2:headers中添加cookie键,值为cookie字符串
import requests
headers = {
"User-Agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1",
"Cookie": "_redmine_session=BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiJTFlMzNjNjdjNWUyOGNkNDY5MjM2M2VmYWE4NGM2MTk2BjsAVEkiEF9jc3JmX3Rva2VuBjsARkkiMUM4N3M1bEJLT204dTlQWFkxcUw2bmRBUy9aSXVNTUxPelNaQTU2TTlreHc9BjsARg%3D%3D--025ba44479499b96abfbfdd8bc77ead00d4f0a4c",
}
# 在使用requests进行请求登录之后才能访问的地址
r = requests.get("http://192.168.0.223:8080/redmine/issues?utf8=%E2%9C%93&set_filter=1&f%5B%5D=status_id&op%5Bstatus_id%5D=%21&v%5Bstatus_id%5D%5B%5D=3&f%5B%5D=assigned_to_id&op%5Bassigned_to_id%5D=%3D&v%5Bassigned_to_id%5D%5B%5D=me&f%5B%5D=created_on&op%5Bcreated_on%5D=%3E%3D&v%5Bcreated_on%5D%5B%5D=2019-03-01&f%5B%5D=&c%5B%5D=project&c%5B%5D=tracker&c%5B%5D=status&c%5B%5D=priority&c%5B%5D=subject&c%5B%5D=assigned_to&c%5B%5D=updated_on&c%5B%5D=parent&c%5B%5D=start_date&c%5B%5D=due_date&c%5B%5D=done_ratio&group_by=")
# 保存页面
with open("renren2.html", "w", encoding="utf-8") as f:
f.write(r.content.decode())
方式3:在请求方法中添加cookies参数,接收字典形式的cookie。字典形式的cookie中的键是cookie的name对应的值,值是cookie的value对应的值。
字典推导式、列表推导式
import requests
headers = {
"User-Agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1",
}
cookies = "_redmine_session=BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiJTFlMzNjNjdjNWUyOGNkNDY5MjM2M2VmYWE4NGM2MTk2BjsAVEkiEF9jc3JmX3Rva2VuBjsARkkiMUM4N3M1bEJLT204dTlQWFkxcUw2bmRBUy9aSXVNTUxPelNaQTU2TTlreHc9BjsARg%3D%3D--025ba44479499b96abfbfdd8bc77ead00d4f0a4c"
cookies = {i.split("=")[0]:i.split("=")[1] for i in cookies.split("; ")}
print(cookies)
# 在使用requests进行请求登录之后才能访问的地址
r = requests.get("http://192.168.0.223:8080/redmine/issues?utf8=%E2%9C%93&set_filter=1&f%5B%5D=status_id&op%5Bstatus_id%5D=%21&v%5Bstatus_id%5D%5B%5D=3&f%5B%5D=assigned_to_id&op%5Bassigned_to_id%5D=%3D&v%5Bassigned_to_id%5D%5B%5D=me&f%5B%5D=created_on&op%5Bcreated_on%5D=%3E%3D&v%5Bcreated_on%5D%5B%5D=2019-03-01&f%5B%5D=&c%5B%5D=project&c%5B%5D=tracker&c%5B%5D=status&c%5B%5D=priority&c%5B%5D=subject&c%5B%5D=assigned_to&c%5B%5D=updated_on&c%5B%5D=parent&c%5B%5D=start_date&c%5B%5D=due_date&c%5B%5D=done_ratio&group_by=",cookies=cookies)
# 保存页面
with open("renren3.html", "w", encoding="utf-8") as f:
f.write(r.content.decode())
17. 不发送post请求,使用cookie获取登录后的页面
- cookie过期时间很长的网站
- 在cookie过期之前能够拿到所有的数据,比较麻烦
- 配合其他程序一起使用,其他程序专门获取cookie,当前程序专门请求页面
chrome分析post和json
- 寻找登录的post地址
-
在form表单中寻找action对应的url地址
- post的数据是input标签中的name的值作为键,真正的用户名密码作为值的字典,post的url地址就是action对应的url地址
-
抓包
- 勾选preserve log按钮,防止页面跳转找不到url地址
- 寻找post数据,确定参数
- 参数不会变,直接用,比如密码不是动态加密的时候
- 参数会变
- 参数在当前的响应中
- 通过js生成
- 定位想要的js
-
选择会触发js事件的按钮,点击event listener,找到js的位置
-
通过chrome中的search all file来搜索url中的关键字
-
添加断点的方式来查看js的操作,通过python来进行同样的操作
- 安装第三方模块
pip install retrying
- 下载安装包,解压,进入解压目录
python3 setup.py install
-
**.whl
,安装方法,pip install **.whl
- requests小技巧
import requests
response = requests.get("http://www.baidu.com")
print(response.cookies)
# 把cookie对象转换为字典
dict = requests.utils.dict_from_cookiejar(response.cookies)
print(dict)
url = "http://tieba.baidu.com/f/index/forumpark?cn=%E9%A6%99%E6%B8%AF%E7%94%B5%E5%BD%B1&ci=0&pcn=%E7%94%B5%E5%BD%B1&pci=0&ct=1&rn=20&pn=1"
# 对地址进行编码
url = requests.utils.quote(url)
print("编码:", url)
# 对地址进行解码
url = requests.utils.unquote(url)
print("解码:", url)
# 请求SSL证书验证
response = requests.get("http://eolinker.guxiansheng.cn/#/index", verify=False)
print(response)
# 设置超时参数
response = requests.get(url, timeout=10)
# 配合状态码判断请求是否成功
assert response.status_code == 200
import requests
from retrying import retry
headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36"}
@retry(stop_max_attempt_number=3)
def _parse_url(url, method, data, proxies):
print("*" * 20)
if method == "POST":
response = requests.post(url, data=data, headers=headers, proxies=proxies)
else:
response = requests.get(url, headers=headers, timeout=3, proxies=proxies)
assert response.status_code == 200
return response.content.decode()
def parse_url(url, method="GET", data=None, proxies={}):
try:
html_str = _parse_url(url, method, data, proxies)
except:
html_str = None
return html_str
if __name__ == '__main__':
url = "www.baidu.com"
print(parse_url(url))