一、什么是爬虫
1、爬虫Spider的概念
爬虫用于爬取数据,又称之为数据采集程序。
爬取的数据来源于网络,网络中的数据可以是由WEB服务器(Nginx/Apache),数据库服务器(MySQL、Redis),索引库(ElastichSearch),大数据(Hbase/Hive),视频/图片库(FTP),云存储(OSS)等提供。
爬取的数据是公开的,非盈利的。
2、Python爬虫
使用Python编写的爬虫脚本(程序)可以完成定时,定量,指定目标(web站点)的数据爬取,主要使用多(单)线程/进程、网络请求库、数据解析、数据存储、任务调度等相关技术。
Python爬虫工程师,可以完成接口测试,功能性测试,性能测试,集成测试。
二、爬虫与WEB后端服务之间的关系
爬虫使用网络请求库,相当于客户端请求,Web后端服务根据请求响应数据。
爬虫即向Web服务器发起HTTP请求,正确的接收响应数据,然后根据数据的类型(Content-Type)进行数据的解析及存储。
爬虫程序在发起请求前,需要伪造一个浏览器(User-Agent指定头),然后再向服务器发起请求,响应200的成功率高很多。
三、Python爬虫技术的相关库
1、网络请求
- urllib
- requests
- selenium(UI自动测试,动态js渲染)
- appium(手机APP的爬虫或UI测试)
2、数据解析
- re正则
- xpath
- bs4
- json
3、数据存储
- pymysql
- mongodb
- elasticsearch
4、多任务
- 多线程(threading)、线程队列 queue
- 协程(asynio、gevent/eventlet)
5、爬虫框架
- scrapy
- scrapy-redis分布式(多机爬虫)
四、常见反爬虫的策略
- UA(User-Agent)策略
- 登录限制(Cookie)策略
- 请求频次(IP代理)策略
- 验证码(图片-云打码,点触验证、滑块)策略
- 动态js策略(Selenium/Splash/api接口)策略
五、爬虫库urllib
1、urllib.request模块
1.1、简单请求
from urllib.request import urlopen # 发起网络请求 resp = urlopen("http://www.hao123.com") assert resp.code == 200 print("请求成功") # 保存请求的网页
# f变量接收open()函数返回对象的__enter__()返回的就结果
with open("hao123.html", "wb") as f: f.write(resp.read())
urlopen(url, data=None)可以直接发起url请求,如果data不为空时,默认是POST请求,反之为GET请求。
resp是http.client.HTTPResponse类对象
1.2、带请求头的请求
""" 初次使用urllib实现爬虫的数据请求 urllib.request.urlopen(url) 发起get请求 urllib.parse.quote() 讲中文进行url编码 urllib.request.urlretrieve(url, filename) 下载url保存到filename """ from urllib.request import urlopen, urlretrieve, Request from urllib.parse import quote import ssl ssl._create_default_https_context = ssl._create_unverified_context def search_baidu(wd='李志'): # 网络请求资源接口(url) url = 'https://www.baidu.com/s?wd=%s' # 生成请求对象,封装请求的url和头header request = Request(url % quote(wd), headers={ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36', 'Cookie': 'PSTM=1577115106; BAIDUID=94178A0C4E70392F8094399EE47D06E8:FG=1; BIDUPSID=66B8977AF3BB990218FACD41E237E148; BDUSS=o2amI1SjlZQUZHOTJiTzZoRWVwN051Z2pOSm5hRm12OGd6YUx4VFRaUmtvbkZlRVFBQUFBJCQAAAAAAAAAAAEAAAAi-ccWaHVpeWljaGFubWlhbgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQVSl5kFUpeMX; BD_UPN=12314753; BD_HOME=1; H_PS_PSSID=30974_1429_21108_30997_30824_30717; delPer=0; BD_CK_SAM=1; PSINO=6; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; COOKIE_SESSION=93270_0_5_4_10_6_0_0_4_3_0_3_93360_0_4_0_1583674230_0_1583674226%7C9%230_2_1577625718%7C1; sug=3; sugstore=0; ORIGIN=0; bdime=0; H_PS_645EC=ee56%2Bh2%2FYYj4EN%2FXrG%2FdO5beNAv52Z9Yz4fZg21AWgMroxtYWtP17XRR1VM' }) response = urlopen(request) assert response.code == 200 print("请求成功") # 读取响应的数据 bytes_ = response.read() with open('%s.html' % wd, "wb") as file: file.write(bytes_)
2、urllib.parse模块
此模块有两个核心函数:
quote()仅对中文字符穿进行url编码
urlencode()可以针对一个字典中所有的values进行编码,然后转成key=value&key=value字符串。
from urllib.parse import quote from urllib.parse import urlencode quote("李志") '%E6%9D%8E%E5%BF%97' urlencode({"姓名":"李志"}) '%E5%A7%93%E5%90%8D=%E6%9D%8E%E5%BF%97'
六、百度翻译的实现
''' 应用:百度翻译 urllib.request.Request urllib.request.urlopen() urllib.parse.urlencode() 发起post请求 ''' import json from urllib.parse import urlencode from urllib.request import Request, urlopen url = 'https://fanyi.baidu.com/sug' headers = { 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' '(KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36', 'x-requested-with': 'XMLHttpRequest', 'cookie': 'PSTM=1577115106; BAIDUID=94178A0C4E70392F8094399EE47D06E8:FG=1; ' 'BIDUPSID=66B8977AF3BB990218FACD41E237E148; BDUSS=o2amI1SjlZQUZHOTJiTzZo' 'RWVwN051Z2pOSm5hRm12OGd6YUx4VFRaUmtvbkZlRVFBQUFBJCQAAAAAAAAAAAEAAAAi-ccWaHVpeWl' 'jaGFubWlhbgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQVSl5kFUpeMX;' ' to_lang_often=%5B%7B%22value%22%3A%22en%22%2C%22text%22%3A%22%u82F1%u8BED%22%7D%2C%7B%22valu' 'e%22%3A%22zh%22%2C%22text%22%3A%22%u4E2D%u6587%22%7D%5D; REALTIME_TRANS_SWITCH=1; HISTORY_SWITCH=1;' ' FANYI_WORD_SWITCH=1; SOUND_PREFER_SWITCH=1; SOUND_SPD_SWITCH=1; delPer=0; PSINO=6; ' 'BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; BDRCVFR[dG2JNJb_ajR]=mk3SLVN4HKm; ' 'BDRCVFR[-pGxjrCMryR]=mk3SLVN4HKm; H_PS_PSSID=30974_1429_21108_30997_30824; ' 'Hm_lvt_64ecd82404c51e03dc91cb9e8c025574=1581948391,1583753885; ' 'from_lang_often=%5B%7B%22value%22%3A%22zh%22%2C%22text%22%3A%22%u4E2D%u6587%22' '%7D%2C%7B%22value%22%3A%22dan%22%2C%22text%22%3A%22%u4E39%u9EA6%u8BED%22%7D%2C%7B%2' '2value%22%3A%22en%22%2C%22text%22%3A%22%u82F1%u8BED%22%7D%5D; Hm_lpvt_64ecd82404c51e03dc91cb' '9e8c025574=1583753974; __yjsv5_shitong=1.0_7_f938cb5bed75810c7f54daa26e4d74d416c7_300_1583753976124_' '219.145.32.239_2b22827f;yjs_js_security_passport=afa2db6dc7814c5d0875abbf087495d6b1b9b20f_1583753976_js' } def fanyi(kw): data = { 'kw': kw } # Request()中的data参数是byte类型 # data不为空时,就是post请求 req = Request(url, data=urlencode(data).encode("utf-8")) resp = urlopen(req) assert resp.code == 200 json_data = resp.read() # byte content_encode = resp.getheader("Content-Type") content_encode = "utf-8" if content_encode is None else content_encode.split("=")[-1] return json.loads(json_data.decode(content_encode)) # loads函数将json字串变为字典 if __name__ == '__main__': print(fanyi("orange"))
七、GET请求多个页面
""" 复杂的get请求,多页面请求下载 """ import random import time from urllib.request import Request, urlopen from urllib.parse import urlencode url = "https://www.baidu.com/s?" params = { 'wd': '', 'pn': 0 # 0, 10, 20, 30...= (n-1)*10 } headers = { 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' '(KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36', 'x-requested-with': 'XMLHttpRequest', 'cookie': 'PSTM=1577115106; BAIDUID=94178A0C4E70392F8094399EE47D06E8:FG=1; ' 'BIDUPSID=66B8977AF3BB990218FACD41E237E148; BDUSS=o2amI1SjlZQUZHOTJiTzZo' 'RWVwN051Z2pOSm5hRm12OGd6YUx4VFRaUmtvbkZlRVFBQUFBJCQAAAAAAAAAAAEAAAAi-ccWaHVpeWl' 'jaGFubWlhbgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQVSl5kFUpeMX;' ' to_lang_often=%5B%7B%22value%22%3A%22en%22%2C%22text%22%3A%22%u82F1%u8BED%22%7D%2C%7B%22valu' 'e%22%3A%22zh%22%2C%22text%22%3A%22%u4E2D%u6587%22%7D%5D; REALTIME_TRANS_SWITCH=1; HISTORY_SWITCH=1;' ' FANYI_WORD_SWITCH=1; SOUND_PREFER_SWITCH=1; SOUND_SPD_SWITCH=1; delPer=0; PSINO=6; ' 'BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; BDRCVFR[dG2JNJb_ajR]=mk3SLVN4HKm; ' 'BDRCVFR[-pGxjrCMryR]=mk3SLVN4HKm; H_PS_PSSID=30974_1429_21108_30997_30824; ' 'Hm_lvt_64ecd82404c51e03dc91cb9e8c025574=1581948391,1583753885; ' 'from_lang_often=%5B%7B%22value%22%3A%22zh%22%2C%22text%22%3A%22%u4E2D%u6587%22' '%7D%2C%7B%22value%22%3A%22dan%22%2C%22text%22%3A%22%u4E39%u9EA6%u8BED%22%7D%2C%7B%2' '2value%22%3A%22en%22%2C%22text%22%3A%22%u82F1%u8BED%22%7D%5D; Hm_lpvt_64ecd82404c51e03dc91cb' '9e8c025574=1583753974; __yjsv5_shitong=1.0_7_f938cb5bed75810c7f54daa26e4d74d416c7_300_1583753976124_' '219.145.32.239_2b22827f;yjs_js_security_passport=afa2db6dc7814c5d0875abbf087495d6b1b9b20f_1583753976_js' } def pages_get(wd): params['wd'] = wd for page in range(1, 101): params['pn'] = (page-1) * 10 page_url = url + urlencode(params) resp = urlopen(Request(page_url, headers=headers)) assert resp.code == 200 filename = "baidu_pages/%s-%s.html" % (wd, page) time.sleep(random.random()) with open(filename, 'wb') as f: bytes_ = resp.read() f.write(bytes_) print('下载%s页成功' % page) if __name__ == '__main__': pages_get("李志")
八、Handler处理器
request对象不能携带cookie,也不能使用代理,所以引入了Handler处理器、自定义Opener。
1、步骤
创建Handler对象
handler = urllib.request.HTTPHandler()
创建opener对象
opener= urllib.request.build_opener(handler)
创建Request对象
request = urllib.request.Request(url=url,headers=headers
发送Reques请求
response = opener.open(request)
九、cookie库
1、cookie库能干啥?
自动帮我们保存登陆的cookie信息
2、cookie库配置
创建一个CookieJar对象
from http.cookiejar import CookieJar cookie = cookiejar.CookieJar()
使用cookiejar对象,创建一个handler对象
handler = urllib.request.HTTPCookieProcessor(cookie)
使用handler创建一个opener
opener = urllib.request.build_opener(handler)
通过opener登录
handler会自动的保存登录之后的cookie
import urllib.request import http.cookiejar as cookiejar post_url = 'http://www.renren.com/ajaxLogin/login?1=1&uniqueTimestamp=20182122180' data = { 'rkey':'1c7df63368df7ce73c234de26178ec11', 'password':'19870115', 'origURL':'http://www.renren.com/home', 'key_id':'1', 'icode':'', 'f':'http://www.renren.com/224549540', 'email':'dqsygcz@126.com', 'domain':'renren.com', 'captcha_type':'web_login', } headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36', 'Referer': 'http://www.renren.com/SysHome.do' } # 转换字节 data = urllib.parse.urlencode(data).encode('utf-8') # 定制请求对象 request = urllib.request.Request(url=post_url, headers=headers, data=data) # 创建cookie对象 cookie = cookiejar.CookieJar() # 创建handler对象 handler = urllib.request.HTTPCookieProcessor(cookie) # 创建opener对象 opener = urllib.request.build_opener(handler) # 使用opener对象发送请求 response = opener.open(request) print(response.getcode())
十、代理服务器
1、什么是代理服务器
一种重要的服务器安全功能,它的工作主要在开放系统互联(OSI)模型的会话层,从而起到防火墙的作用
翻墙,是指绕过相应的IP封锁、内容过滤、域名劫持、流量限制等。
2、代理的常用功能
突破自身IP访问限制,访问国外站点。
访问一些单位或团体内部资源
提高访问速度,通常代理服务器都设置一个较大的硬盘缓冲区,当有外界的信息通过时,同时也将其保存到缓冲区中,当其他用户再访问相同的信息时, 则直接由缓冲区中取出信息,传给用户,以提高访问速度。
隐藏真实IP。
3、代码配置
创建Reuqest对象
创建ProxyHandler对象
用handler对象创建opener对象
使用opener.open函数发送请求
# 创建代理handler对象 handler = urllib.request.ProxyHandler(proxies={'http':'114.212.80.2:3128'}) # 创建opener opener = urllib.request.build_opener(handler) # opener.open 替代 urllib.request.urlopen response = opener.open(request) with open('ipp.html','wb') as fp: fp.write(response.read())