URI 统一资源标识符, URL 统一资源定位符
URL是URI的子集,每个URL都是URI,URI还包括一个子类叫URN,统一资源名称,URN只命名资源而不定位资源,如书的 ISBN。在目前的互联网中,URN非常少,几乎所有的URI都是URL
浏览器中看到的网页都是超文本解析而成的,源代码是一系列的HTML代码,HTML就可以称作超文本
URL的开头有http或https,有时还有ftp,sftp,smb开头的URL,他们都是协议类型,通常抓取的页面就是http或https协议的
HTTP 超文本传输协议,从网络传输超文本到本地浏览器的传送协议,保证高效而准确地传送超文本文档,由万维网协会和internet工作小组IETF共同合作制定的规范。
HTTPS 是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入SSL层,简称HTTPS
HTTPS的安全基础是SSL,因此通过它传输的内容都是经过SSL加密的。
某些网站虽然使用了HTTPS协议,但还是会被浏览器提示不安全,如 https://www.1230cn ,因为12306的CA证书是由中国铁道部自行签发的,而这个证书是不被CA机构信任的,但它的数据实际上是经过SSL加密的。如果要爬取这样的站点,就要设置忽略证书的选项,否则会提示SSL链接错误
我们在浏览器中输入一个URL,回车之后便会看到页面内容,实际上,这个过程是浏览器向网站所在的服务器发送了一个请求,网站服务器接收到这个请求后进行处理和解析,然后返回响应内容,接着传送会浏览器,响应里包含了页面的源代码等内容,浏览器再对其进行解析,使网页呈现了出来。在Network页面下方的一个个条目代表着一个个发送请求和接收响应的过程。
Name:请求名称,一般会把URL的最后一部分当作名称
Status:响应的状态码,显示为200代表响应是正常的
Type:请求的文档类型,document代表请求的是一个HTML文档,内容就是一些HTML代码
Initiator:请求源,用来标记请求是哪个对象或进程发起的
Size:从服务器下载的文件和请求的资源大小,如果是从缓存中取得的资源则会显示为 from catch
Time:发起请求到获取响应所用的总时间
Waterfall:网络请求的可视化流瀑布
点击条目可以看到更详细的信息
首先是General部分,Request为URL请求的URL,Request Method为请求的方法,Status Code为响应的状态码,Remote Address为远程服务器的地址和端口,Referrer Policy 为 Referrer 判别策略。再往下是Response Headers 和Request Headers,代表响应头和请求头,服务器会根据请求头内的信息判断请求是否合法,进而作出对应的响应。
常见的请求方法由两种,GET和POST
在浏览器中输出URL并回车就发起了一个GET请求,请求的参数会直接包含到URL里,POST请求大多在表单提交时发起,比如对于一个登录表单,输入用户名和密码后点击登录,通常会发起一个POST请求,数据以表单的形式传输,而不会体现在URL中。
对比如下:
1.GET请求的参数包含在URL里,数据可以在URL里看到,而POST请求的URL不会包含这些数据,数据都是通过表单的形式传输的,会包含在请求体中
2.GET请求提交的数据最多只有1024字节,而POST方式没有限制
除GET和POST之外,还有一些请求方法如HEAD,PUT,DELETE,OPTIONS,CONNECT,TRACE等
请求的网址即统一资源定位符 URL,它可以唯一确定我们想请求的资源
请求头用来说明服务器要使用的附加信息
Accept:请求报头域,指定客户端可接受哪些类型的信息
Accept-Encoding:指定客户端可接受的内容编码
Accept-Language:指定客户端可接收的语言类型
Host:用于指定请求资源的主机IP和端口号,其内容为请求URL的原始服务器或网关的位置
Cookie:也常用复数形式Cookies,这是网站为了辨别用户进行会话跟踪而存储在用户本地的数据
Reference:用来标识这个请求是从哪个页面发过来的,服务器可以拿到这一信息并做相应的处理,如做来源统计,防盗链处理
User-Agent:简称UA,它是一个特殊字符串头,可以使服务器识别客户使用的操作系统及版本,浏览器及版本信息
Content-Type:也叫互联网媒体类型或者MME类型,如text/html代表HTML格式,image/gif代表GIF图片,application/json代表JSON类型
请求体一般承载的内容是POST请求中的表单数据,而对于GET请求,请求体则为空
Content-Type和POST提交数据的方式有关,在爬虫中,如果要构造POST请求,需要使用正确的Content-Type类型,并了解各种请求库的各个参数设置时使用的时哪种Content-Type,不然可能会导致POST提交后无法正常响应
响应状态码表示服务器的响应状态,在爬虫中可以根据状态码来判断服务器的响应状态
Date:标识响应产生的时间
Last-Modified:指定资源的最后修改时间
Content-Type:文档类型,指定返回的数据类型
Set-Cookie:设置Cookie。响应头中的Set-Cookie告诉浏览器需要将此内容放在Cookies中,下次请求携带Cookies请求
Expires:指定响应的过期时间,可以使代理服务器或浏览器将加载的内容更新到缓存中,如果再次访问时,就可以直接从缓存中加载,降低服务器负载,缩短加载时间。
在开发者工具中点击Preview,就可以看到网页源代码,也就是响应体的目标
网页可以分为三大部分:HTML,CSS 和 JavaScript 。如果网页是一个人的话,HTML 是骨架,CSS是皮肤,JS是肌肉。
HTML全称是超文本标记语言,它是描述网页的一种语言,各种复杂的元素用不同类型的标签来表示,各种标签通过不同的排列和嵌套形成了网页的框架
CSS叫层叠样式表通过选择器和样式规则定义每个标签的样式
JavaScript简称JS,是一种脚本语言,网页中的一些交互和动画效果,如下载进度条,提示框,轮播图等就是JS的功劳,它定义了网页的行为,实现了用户的信息直接的实时动态和交互
在HTML中,所有标签定义的内容都是节点,它们构成了一个HTML DOM树
DOM是W3C(万维网联盟)的标准,全称是文档对象模型,它定义了HTML和XML文档的标准,根据HTML DOM的规定,HTML文档中的所有内容都是节点,整个文档是一个文档节点,每个HTML元素是元素节点,HTML元素内的文本是文本节点,每个HTML属性是属性节点,注释是注释节点
选择器
通过urllib、requests等可以帮助我们实现HTTP请求操作,请求和响应都可以用类库提供的数据结构来表示,得到响应部分后只需要解析数据结构中的Body部分即可,即得到网页的源代码
最通用的方法是采用正则表达式提取,这是一个万能的方法,但是比较复杂且容易出错。另外由于网页的结构由一定的规则,所有可以根据网页节点属性,CSS选择器或Xpath来提取网页信息的库,如BeautifulSoup,pyquery,lxml等
提取信息后,一般会将提取到的数据保存到某处以便后续使用。这里保存的形式由多种多样,如可以简单保存为TXT文本或JSON文本,也可以保存到数据库,如MySQL和MongoDB等,也可以保存至远程服务器,如借助SFTP进行操作等
爬虫就是代替我们快速获取大量数据的自动化程序,它可以在抓取过程中进行各种异常处理,错误重试等操作,确保爬取高效进行
在网页中能看到各种各样的信息,最常见的便是常规网页,它们对应着HTML代码,而最常见的便是HTML源代码,另外还有些网页返回的不是HTML代码,而是一个JSON字符串,这种格式方便传输和解析,数据提取也更方便。此外,还有各种二进制数据,如图片,视频,音频,也可以利用爬虫爬取并保存,还有其他各种扩展名的文件,只要在浏览器里可以访问到,就可以将其抓取下来
有时候,用urllib或requests抓取网页时,得到的源代码实际和浏览器中看到的不一样,这是一个非常常见的问题。现在网页越来越多地采用 Ajax,前端模块化工具来构建,整个网页可能都是JS渲染出来的,也就是说原始的HTML代码就是一个空壳。对于这样的情况,可以分析其后台的Ajax接口,也可以使用Selenium,Splash这样的库来实现 JS 渲染。
在浏览网站时,经常遇到需要登录的情况,有些页面只有登录才能访问,而且登录之后可以连续访问很多网站,但有时候过一段时间就需要重新登录,还有一些网站在打开浏览器时就自动登录了,而且很长时间都不会失效,这些就涉及了会话(Session)和Cookies的相关知识
一个基本的HTML代码将其保存为一个.html文件后把它放在某台具有固定公网IP的主机上,主机装有Apache或Nginx等服务器,这台主机就可以作为服务器了,其他人便可以通过访问服务器而看到这个页面,这就搭建了一个网站。这种网页是HTML代码编写的,文字,图片等内容均通过写好的HTML代码来指定,这种页面叫做静态页面,它加载速度快,编写简单,但存在很大的缺陷,如可维护性差,不能根据URL灵活多变地显示内容。因此动态网页应运而生,它可以动态解析URL中参数的变化,关联数据库并动态呈现不同的页面内容,它们不再是一个简单的HTML,而可能是由JSP,PHP,Python等语言编写的,其功能更丰富,另外动态网站还可以实现用户登录和注册的功能。
HTTP的无状态是指HTTP协议对事务处理是没有记忆能力的,也就是说服务器不知道客户端是什么状态,当我们向服务器发送请求后,服务器解析此请求,然后返回对应的响应,服务器负责完成整个过程,而且这个过程是完全独立的,服务器不会记录前后的状态变化,也就是缺少状态记录,这意味着如果后续要处理前面的信息,则必须重传,着导致需要额外传递前面的一些重复请求,才能获取后续响应,这太浪费资源,而且对于需要用户登录的页面更是棘手。
这时两个用于保持HTTP连接状态的技术就出现了,它们分别是会话和Cookies。会话在服务端,也就是网站的服务端,用来保存用户的会话信息;Cookies在客户端,也可以理解为浏览器端,有了Cookies,浏览器在下次访问网页时会自动附带上它发送给服务器,服务器通过识别Cookies并鉴定是哪个用户,然后判断用户是否是登录状态,然后返回对应的响应。
会话,其本来的含义是指有始有终的一系列动作/消息。比如,打电话时,从拿起电话拨号到挂断电话这中间的一系列过程可以称为一个会话。而在Web中,会话对象用来存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序和Web页之间跳转时,存储在会话对象中的变量不会丢失,而是在整个用户会话中一直存在下去,当用户请求来自应用程序的Web页时,如果该用户还没有会话,则Web服务器将会自动创建一个会话对象。当会话过期或被放弃后,服务器将终止该会话。
Cookies指某些网站为了识别用户身份,进行会话跟踪而存储在用户本地终端上的数据。
当客户端第一次请求服务器端时,服务器会返回一个请求头中带有Set-Cookie字段的响应给客户端,用来标记是哪一个客户,客户端浏览器会把Cookies保存起来。当浏览器下一次再请求该网站时,浏览器会把此Cookies放到请求头一起提交给服务器,Cookies携带了会话的ID信息,服务器检查该Cookies即可找到对应的会话是什么,然后再判断会话来以此来判断用户状态。
成功登录某个网站时,服务器会告诉客户端要设置哪些Cookies信息,在后续访问页面客户端会把Cookies发送给服务器,服务器再找到对应的会话加以判断。如果会话中的某些设置登录状态的变量是有效的,那就证明用户处于登录状态,此时返回登录之后才能查看的网页内容,浏览器进行解析后即可看到网页的具体内容。
反之,如果传给服务器的Cookies是无效的,或者会话已经过期了,我们将不能继续访问页面,此时可能会收到错误的响应或者跳转到登录页面重新登录。
所以,Cookies和会话需要配合,一个处于客户端,一个处于服务端,二者共同协作,就实现了登录会话控制
在开发者工具中打开Application选项卡,然后在左侧会有一个Storage部分,最后一项即为Cookies,点开的每个条目都可以称为Cookie,它有如下几个属性
Name:该Cookie的名称,一旦创建,该名称便不可更改
Value:该Cookie的值,如果值为Unicode字符,需要为字符编码,如果值为二进制,则需要用BASE64编码
Domain:可以访问该Cookie的域名
Max Age:该Cookie的失效时间,单位为秒,也常和Expires一起使用,通过它们可以计算出其有效时间。Max Age如果为正数,则该Cookie在Max Age秒后失效;如果为负数,则关闭浏览器时Cookie即失效,浏览器也不会以任何形式保存该Cookie
Path:该Cookie的使用路径如果设置为/path/,则只有/path/的页面可以访问该Cookie;如果设为 / ,则本域名下的所有页面可以访问该Cookie
Size:此Cookie的大小
HTTP:Cookie的httponly属性,若为true,则只有HTTP头中会带有此Cookie的信息,而不能通过document,cookie来访问此Cookie
Secure:该Cookie是否仅被使用安全协议传输。安全协议有HTTPS和SSL等,在网络上传输数据之前先将数据加密,默认为faslse
会话Cookie放在浏览器内存里,浏览器在关闭之后改Cookie即失效,持久Cookie则会保存到客户端的硬盘中,下次还可以继续使用,用于长久保存登录状态。其实严格来说没有会话Cookie和持久Cookie之说,至少Cookie的Max Age和 Expires字段决定了过期的时间
除非程序通知服务器删除一个会话,否则服务器会一直保留,即使关闭浏览器,会话可能有也不会消失,之所以会产生 ’关闭浏览器,会话就会消失 ‘的错觉是因为大部分会话机制使用会话Cookie来保存会话ID信息,它在关闭浏览器后就消失了。由于关闭浏览器不会导致会话删除,这就需要服务器会话设置一个失效时间,当距离客户端上一次使用会话超过这个会话的四化建时,服务器就可以认为服务端已经停止了活动,才会把会话删除以节省存储空间
服务器可能会检测某个IP在单位时间内的请求次数,如果超过了阈值,就会直接拒绝服务,返回一些错误信息,这种情况可以称为封IP,可以通过伪装IP来防止
代理实际上是指代理服务器,它的功能是代理网络用户去取得网络信息,形象地说,它是网络信息的中转站。当我们正常请求一个网站时,是仿宋给Web服务器,Web服务器把响应传回给我们。如果设置了代理服务器,此时本机不是直接向Web服务器发起请求,而是发给代理服务器,然后由代理服务器再发给Web服务器,接着代理服务器再把Web服务器返回的响应转发给我们的IP,这就是代理的原理
1.突破自身IP访问限制,访问一些平时不能访问的站点
2.访问一些单位或团体内部资源
3.提高访问速度
4.隐藏真实IP
代理分类
1.使用网上免费代理:现在基本没了
2.使用付费代理:网上存在许多代理商,可以付费使用
3.ADSL拨号:拨一次号换一次IP,稳定性高
urllib库是Python内置的HTTP请求库,官方文档链接:https://docs.python.org/3/library/urllib.html
内置四个模块:
reuquest:最基本的HTTP请求模块,用来模拟发送请求
error:异常处理模块,捕获异常,然后进行重试或其他操作以保证程序不会意外终止
parse:工具模块,提供了许多 URL 处理办法,比如拆分,解析,合并
robotparser:主要用来识别网站的robots.txt文件,判断哪些网站可以爬,哪些网站不可以
request.urlopen(url,data=None,[timeout,]*,cafile=None,capth=None,cadefault=False,context=None)
data参数
import urllib.parse
import urllib.request
data=bytes(urllib.parse.urlencode({'word':'hello'},encoding='utf-8'))
#urlencode()将参数字典转化为字符串,利用 bytes() 转化为字节流
response=urllib.request.urlopen('http://httpbin.org/post',data=data)
# httpbin.org 站点可以测试POST请求,参数出现在 form 字段中,表明模拟了表单提交,以 POST 方式传输数据
print(response.read())
#b'{\n "args": {}, \n "data": "", \n "files": {}, \n "form": {\n "word": "hello"\n }, \n "headers": {\n "Accept-Encoding": "identity", \n "Content-Length": "10", \n "Content-Type": "application/x-www-form-urlencoded", \n "Host": "httpbin.org", \n "User-Agent": "Python-urllib/3.9", \n "X-Amzn-Trace-Id": "Root=1-60248a78-54f8e3225c1776960285ea7d"\n }, \n "json": null, \n "origin": "171.127.247.145", \n "url": "http://httpbin.org/post"\n}\n'
指定 data 参数后,请求方式就由 GET 方式转化为 POST 方式 ,data 接收二进制数据
timeout参数指定超时时间,context 必须是 ssl.SSLContext 类型,指定 SSL 设置
cafile 和capath 指定CA证书和它的路径,请求HTTPS链接时会有用
cadefault 参数现在已经弃用,默认为 False
urllib.request.urlopen()的url参数可以时一个 Request 类型的对象,可以通过 Request 来构造
class urllib.request.Request(url,data=None,headers={},origin_req_host=None,unverifiable=False,method=None)
data参数
见 二.1.1.1 data 参数
headers参数
headers是一个字典,表示请求头,可以在构造请求时通过 headers 参数直接构造,也可以通过请求实例的 add_header()方法添加
添加请求头最常用的方法是通过修改 User-Agent 来伪装浏览器,默认为 Python-urllib
origin_req_host参数
指定请求方的 host 名称 或者 IP 地址
unverifiable参数
此参数表示这个请求是否是无法验证的,默认是False,意思就是说用户没有足够权限来选择接收这个请求的结果
method参数
接收字符串,用来指定请求使用的方法,如 GET ,POST ,PUT 等
from urllib import request,parse
url='http://httpbin.org/post'
dic={
'name':'YS'
}
data=bytes(parse.urlencode(dic),encoding='utf-8')
req=request.Request(url=url,data=data,method='POST')
req.add_header('User-Agent','ozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36')
response=request.urlopen(req)
print(response.read().decode('utf-8'))
'''{
"args": {},
"data": "",
"files": {},
"form": {
"name": "YS"
},
"headers": {
"Accept-Encoding": "identity",
"Content-Length": "7",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "ozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36",
"X-Amzn-Trace-Id": "Root=1-60249488-11afc9ff3518f4db3c71661d"
},
"json": null,
"origin": "171.127.247.145",
"url": "http://httpbin.org/post"
}'''
对于一些高级操作,比如Cookies处理,代理设置等就需要用到 Handler,可以理解为各种处理器
urllib.request模块里的 BaseHandler 类是所有其他 Handler 的父类,提供了最基本的方法
有各种 Handler 子类继承这个 BaseHandler 类:
HTTPDefaultErrorHandler:用于处理 HTTP响应错误,错误都会抛出 HTTPError 类型的错误
HTTPRedirectHandler:用来处理重定向
HTTPCookieProcessor:处理Cookies
ProxyHandler:用来设置代理,默认为空
HTTPPasswordMgr:用来管理密码,它维护了用户名和密码的表
HTTPBasicAuthHandler:处理管理认证,如果打开一个链接时需要认证,可以用它来解决认证问题
更多代理
还有一个比较重要的类就是OpennerDirector,可以称为Openner,要实现更高级的功能,需要深入一层进行配置,要用更底层的实例来完成操作,因此必须引入 Openner。利用 Handler 来构建 Openner 再使用 open()方法
对于需要认证,登录的页面可以借助 HTTPBasicAuthHandler完成
from urllib.request import HTTPPasswordMgrWithDefaultRealm,HTTPBasicAuthHandler,build_openner
from urllib.error import URLError
username='username'
password='password'
url='http://localhost:5000/'
p=HTTPPasswordMgrWithDefaultRealm()
p.add_password(None,url,username,password)
auth_handler=HTTPBasicAuthHandler(p)
#实例化HTTPBasicAuthHandler对象,参数是HTTPPasswordMgrWithDefaultRealm对象,这样建立了一个处理验证的 Handler
opener=build_opener(auth_handler)
try:
result=opener.open(url)
html=result.read().decode('utf-8')
print(html)
except URLError as e:
print(e.reason)
在爬虫时添加代理
'''import urllib.request
def handler_openner():
#系统的urlopen并没有添加IP代理的功能,需要自己定义
#安全 套接层 ssl第三方的CA数字证书
#http 80 端口和https的 443 端口
#urlopen通过handler处理器(IP)和openner请求数据
#创建自己的处理器
handler=urllib.request.HTTPHandler()
#创建自己的openner
openner=urllib.request.build_opener(handler)
#用自己创建的openner调用open方法
respones=openner.open(url)
def creat_proxy_handler():
#添加代理
proxy={
#免费的写法
"http":"http://120.77.249.46:8080",
}
proxy_handler=urllib.request.ProxyHandler(proxy)
openner=urllib.request.build_opener(proxy_handler)
return data=openner.open(url).read()'''
#付费的代理发送
import urllib.request
#1.用户名和密码
#通过验证的处理器来发送
def money_proxy_use():
#一.第一种方法
#1.代理ip
money_proxy={"http":"username:password@IP地址(带端口)"}
#2.创建代理器
proxy_handler=urllib.request.ProxyHandler(money_proxy)
#3.通过处理器创建openner
urllib.request.build_opener(proxy_handler)
#4.open发送请求
openner.open("url")
#二.第二种方法
user_name="adcname"
pwd="123456"
proxy_money="IP地址(带端口)"
#创建密码管理器,添加用户名和密码
password_manager=urllib.request.HTTPasswordMgrWitDefaultRealm()
#url定位 uri>url
#url 资源定位符
password_manager.add_password(None,proxy_money,user_name,pwd)
#创建可以验证代理ip的处理器
handler_auth_proxy=urllib.request.ProxyBasicAuthHandler(password_manager)
#根据处理器创建openner
openner_auth=urllib.request.build_opener(handler_auth_proxy)
#发送请求
response=openner_auth.open("url")
#爬取自己公司的数据,做数据分析 #auth
def auth_nei_wang():
#1.用户名密码
user="admin"
pwd="admin123"
nei_url="http://192.6"
#2.创建密码管理器
pwd_manager=urllib.request.HTTPasswordMgrWitDefaultRealm()
pwd_manager.add_password(None,nei_url,pwd)
#创建认证处理器(用requests更多)
urllib.request.HTTPBasicAuthHandler(pwd_manager)
auth_handler=urllib.requestHTTPBasicAuthHandler(pwd_manager)
openner=urllib.request.build_opener(auth_handler)
response=openner.open(nei_url)
处理Cookies
import http.cookiejar,urllib.request
cookie=http.cookiejar.CookieJar()
handler=urllib.request.HTTPCookieProcessor(cookie)
opener=urllib.request.build_opener(handler)
response=opener.open('http://www.baidu.com')
for item in cookie:
print(item.name+'='+item.value)
'''
BAIDUID=A03FA96987144827144869366D258EEC:FG=1
BIDUPSID=A03FA9698714482759ECC9C7AB0B34D3
H_PS_PSSID=33425_33403_33344_33461_33584_26350_33544
PSTM=1613012166
BDSVRTM=0
BD_HOME=1
'''
#输出Cookie文本格式
filename='cookie.txt'
cookie=http.cookiejar.MozillaCookieJar(filename)
#cookie=http.cookiejar.LWPCookieJar(filename)
#保存格式不同,保存成libwww-perl(LWP)格式的Cokies
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)
利用libwww-perl(LWP)格式的Cokies的Cookies
cookie=http.cookiejar.LWPCookieJar()
#读取本地的Cookies文件,获取Cookies的内容,
cookie.load('cookie.txt',ignore_discard=True,ignore_expires=True)
handler=urllib.request.HTTPCookieProcessor(cookie)
opener=urllib.request.build_opener(handler)
response=opener.open('http://www.baidu.com')
URLError类来自urllib库的error模块,继承自OSError类,是error异常模块的基类,由 request模块产生的异常都可以通过捕获这个类来处理,它由一个属性 reason 属性,即返回错误的原因
from urllib import request,error
try:
response=request.urlopen('https:jiade.com')
except error.URLError as e:
print(e.reason)
# no host given
它是URLError的子类,专门用来处理HTTP请求错误,比如认证失败,请求失败等,它由三个属性:
code:返回HTTP状态码
reason:同父类一样,返回错误原因
headers:返回请求头
改方法可以实现 URL 的识别和分段
from urllib.parse import urlparse
result=urlparse('http:baidu.com/index.html;user?id=#comment')
print(type(result),result)
# ParseResult(scheme='http', netloc='', path='baidu.com/index.html', params='user', query='id=', fragment='comment')
#scheme代表协议,netloc代表域名,path代表路径,params代表参数,query代表查询条件,#为锚点,直接定位页面的下拉部分
url.parse.urlparse(urlstring,scheme=‘’,allow_fragments=True)
urlstring:必填项,待解析的URL
scheme:默认的协议,假如URL链接没有带协议信息,会将这个作为默认的协议
allow_fragments:是否忽略 fragment 如果设置为 False,fragment部分会被忽略,它会被解析为path,parameters或query的一部分
它接收一个可迭代对象,长度必须是 6 ,用于构造 URL
from urllib.parse import urlunparse
data=['http','www.baidu.com','index.html','user','a=6','comment']
print(urlunparse(data))
#http://www.baidu.com/index.html;user?a=6#comment
此方法和urlparse()方法非常类似,只是它不再单独解析params这一部分,只返回 5 个结果,params会合并到path中
from urllib.parse import urlsplit
result=urlsplit('http:baidu.com/index.html;user?id=#comment')
print(result)
#SplitResult(scheme='http', netloc='', path='baidu.com/index.html;user', query='id=', fragment='comment')
传入长度为5的可迭代对象,组成完整url
有了 urlunparse()和 urlsplit()方法可以完成链接的合并,但必须有固定的长度
urljoin()是生成链接的另一个方法,该方法需要base_url(基础链接)和新链接,分析base_url的scheme,netloc和path三个内容,并对新链接缺少的部分进行补充。如果新的连接里这三项不存在,就予以补充,如果存在就使用新连接的部分,而base_url终的params,query和 fragment是不起作用的
urlencode()可以将字典转化为 GET 请求的参数
from urllib.parse import urlencode
params={
'name':'YS',
'age':'20'
}
base_url='http://www.baidu.com?'
url=base_url+urlencode(params)
print(url)
#http://www.baidu.com?name=YS&age=20
和urlencode()相反,将参数内容转化为字典
from urllib.parse import parse_qs
query='name=YS&age=20'
print(parse_qs(query))
#{'name': ['YS'], 'age': ['20']}
与parse_qs()不同的是它将参数转化为元组组成的列表
from urllib.parse import parse_qsl
query='name=YS&age=20'
print(parse_qsl(query))
#[('name', 'YS'), ('age', '20')]
该方法将内容转化为 URL 编码的格式,当 URL 中带有中文参数时,可能会导致乱码
from urllib.parse import quote
keyword='壁纸'
url='https://www.baidu.com/s?wd='+quote(keyword)
print(url)
#https://www.baidu.com/s?wd=%E5%A3%81%E7%BA%B8
进行 URL 解码
from urllib.parse import unquote
url='https://www.baidu.com/s?wd=%E5%A3%81%E7%BA%B8'
print(unquote(url))
#https://www.baidu.com/s?wd=壁纸
import requests
files={'file':open('favicon.ico','rb')}
r=requests.post('http://httpbin.org/post',files=files)
文件上传部分会有一个单独的files字段来标识内容,而不是在form字段中
import requests
import requests
headers={
'Cookie':'_zap=33c39d33-374f-48fd-a2de-6358a98d1e07; d_c0="AABckRdTjhKPTj7J97KhNpb9anaLYv7UOxw=|1611568886"; _xsrf=Jha8143Tiu2AZ8QgEOj2dBW6UEYaPYhA; tst=r; q_c1=0fc228405a8340768a9e6d99c171eb89|1612782635000|1612782635000; l_cap_id="M2YzNTBmNzk1ZGY0NDhjMTljMTdmMGRjMTdiYmQ3YjY=|1612782771|f97884e2c6576a0d5947c823a9c57bc37b2149b0"; r_cap_id="OTk5Y2RmOThhYmM4NDc4OWE3OGJiZjdiMTVhODBhOTQ=|1612782771|1a1a13ff5701d07d06794c025acc921e2f846ddb"; cap_id="YTU2ODE5MzgzY2IzNGExN2IyMWQ2ZTU0MWUwMDc2ZjQ=|1612782771|a9f09dbccd13f358479bf7cfdea68150a44ee1db"; captcha_session_v2="2|1:0|10:1612838700|18:captcha_session_v2|28:YzBndmViNnYzanZqa3VycGZqa2c=|6628743e8ee12336328d103475a52b969e71672b64236d9ec8cae6e74e6099ad"; captcha_ticket_v2="2|1:0|10:1612838707|17:captcha_ticket_v2|244:eyJhcHBpZCI6IjIwMTIwMzEzMTQiLCJyZXQiOjAsInRpY2tldCI6InQwM1A0S2tmZ2tPb1NwWi1DWF8yUExVQjBjd3phUElTRnM5bkNReDNiZU9FV0NfdXV5UE1VbEp3NDN2UWJ2VlZZSGtMeG80enlfc0R3ZTdpNVhEazZ6YVVhSTNoWXpDNjZDSU5pM1EwTXdpRzJ2cThzenU4bWtUUXcqKiIsInJhbmRzdHIiOiJAWE1zIn0=|c3e2e53e522c1abbbb247985cf25508f535bd3bee5e4fbc6c8b591826eace6cb"; z_c0="2|1:0|10:1612838708|4:z_c0|92:Mi4xc1Zsb0lBQUFBQUFBQUZ5UkYxT09FaVlBQUFCZ0FsVk5ORVVQWVFDbEFhLVdzT0pPcXlDcmNfODJBTlMwRENFMGhn|f3c39bbc0459475fbeb0d7587b2a8fc4fc17c1907412450dfe26ece37f1b0f81"; Hm_lvt_98beee57fd2ef70ccdd5ca52b9740c49=1612868386,1612929750,1612959789,1613030732; Hm_lpvt_98beee57fd2ef70ccdd5ca52b9740c49=1613030739; KLBRSID=fe78dd346df712f9c4f126150949b853|1613030739|1613030730',
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36'
}
r=requests.get('https://www.zhihu.com/people/sen-luo-mo-xiang-90-46',headers=headers)
print(r.text)
import requests
login_url="https://www.yaozh.com/login"
member_url="https://www.yaozh.com/member/"
headers={
"User-Agent":"Mozilla/5.0 (Linux; Android 0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Mobile Safari/537.36"
}
login_form_data={
"username":"YS12345",
"pwd":"ys12345",
"formhash":"B2A8BFFA91",
"backurl":"https%3A%2F%2Fwww.yaozh.com%2F"
}
session=requests.session()#类似于cookiejar功能自动保存cookie
#2.登录成功后带着有效的cookies访问请求目标
login_response=session.post(login_url,data=login_form_data)
data=session.get(member_url,headers=headers).content.decode()
with open("C:/Users/PC/Desktop/try.html","w",encoding="utf-8") as f:
f.write(data)
import requests
#忽略证书认证
response=requests.get('https://www.1230cn',verify=False)
print(response.status_code)
#InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised
#200
import requests
proxies={
'http':'http://10.10.1.10:3128'
'https':'https://10.10.1.10:1080'
}
response=requests.get('https://www.taobao.com',proxies=proxies)
如果代理需要使用 HTTP Basic Auth,可以用类似 http://user:password@host:port
import requests
proxies={
'http':'http://user:[email protected]:3128/'
}
response=requests.get('https://www.taobao.com',proxies=proxies)
除了基本的HTTP代理外,requests还支持SOCKS协议的代理
import requests
proxies={
'http':'socks5://user:password@host:port'
'https':'socks5://user:password@host:port'
}
response=requests.get('https://www.taobao.com',proxies=proxies)
import requests
from requests.auth import HTTPBasicAuth
auth=HTTPBasicAuth('username','password')
r=get('http://localhost:5000',auth=auth)
#或者直接使用
#r=get('http://localhost:5000',auth=('username','password'))
此外requests还提供了其他认证方式,如 OAuth 认证
在urllib库中,我们可以将请求表达为一个数据结构,其中各个参数都可以通过一个Request对象来表示,在requests中这个数据结构就是Prepared Request
from requests import Request,Session
url='http://httpbin.org/post'
data={
'name':'YS'
}
headers={
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36'
}
s=Session()
#构造 Request对象
req=Request('POST',url=url,data=data,headers=headers)
#转化为Prepared Request对象
prepped=s.prepare_request(req)
r=s.send(prepped)
print(r.text)
'''
{
"args": {},
"data": "",
"files": {},
"form": {
"name": "YS"
},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Content-Length": "7",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36",
"X-Amzn-Trace-Id": "Root=1-6024f412-161d7a69506d9cdf6ab02532"
},
"json": null,
"origin": "171.127.247.145",
"url": "http://httpbin.org/post"
}
'''
补充节点轴选择
from lxml import etree
import requests
r=requests.get('https://www.baidu.com')
text=r.content.decode('utf-8')
html=etree.HTML(text)
#选中第一个a节点的所有祖先节点
reslt=html.xpath('//a[1]/ancestor::*')
print(reslt)
#[, , , , , , , , , , ]
#选中第一个a节点的所有div祖先节点
result=html.xpath('//a[1]/ancestor::div')
print(result)
#[, , , , , ]
#选中第一个a节点的所有属性节点
result=html.xpath('//a[1]/attribute::*')
#选中第一个a节点的所有href属性为"link1.html"的子节点
result=html.xpath('//a[1]/child::a[@href="link1.html"]')
#获取第一个a节点的所有span子孙节点
result=html.xpath('//a[1]/descendant::span')
#获取第一个a节点后续所有节点中的第二个后续节点
result=html.xpath('//a[1]/following::*[2]')
#获取第一个a节点之后的所有后续同级节点
result=html.xpath('//a[1]/following-sibling::*')
#1.轻量级的数据交互格式 2.对象用花括号表示 数组用方括号表示 3.必须用双引号 4.结尾不能有逗号 5.不能写注释 6.整个文件有且只有一个花括号或者方括号
import json
#1.字符串和字典,列表转换(+S)
#字符串(json)-->dict list
data='[{"name":"YS","age":"20"},{"name":"LS","age":"18"}]'
list_data=json.loads(data)
print(type(data)) #str
print(type(list_data)) #list 去掉外侧单引号且所有双引号转换为单引号
#dict list-->字符串
list2=[{"name":"YS","age":"20"},{"name":"LS","age":"18"}]
data_json=json.dumps(list2)
#.文件和字符串,列表转换
#写入json文件
json.dump(list2,open(path,"w"))
#读取json-->list dict
result=json.load(open(path))
import csv
#需求 json-->csv
#1.读,创建文件
json_fp=open(path,"r",encoding="utf-8")
csv_fp=open(path,"w",)
#2.提出表头和表的内容
data_list=json.load(json_fp)
sheet_title=data_list[0].keys()
sheet_data=[data.values() for data in data_list]
#3.csv写入器
writer=csv.writer(csv_fp)
#4.分别写入表头和内容
writer.writerow(sheet_title)
writer.writerows(sheet_data)
#5.关闭文件
json_fp.close()
csv_fp.close()
import csv
#newline=''可以消除空行
with open('C:/Users/PC/Desktop/test.csv','w',newline='') as c:
#初始化写入对象
writer=csv.writer(c)
#delimiter参数指定列与列间的分隔符,delimiter=' '使数据在一列内显示没个数据直接是空格
#writer=csv.writer(c,delimiter=' ')
writer.writerow(['id','name','age'])
writer.writerow(['1','Mike','20'])
writer.writerow(['2','Bob','35'])
#writerows支持多行写入,参数是二维列表
#writer.writerows([['1','Mike','20'],['2','Bob','35']])
'''
支持字典类型写入
fieldnames=['id','name','age']
writer=csv.DictWriter(c,fieldnames=fieldnames)
writer.writeheader()
writer.writerow({'id':'1','name':'Mike','age':'20'})
writer.writerow({'id':'2','name':'Bob','age':'35'})
'''
import csv
with open('C:/Users/PC/Desktop/test.csv','r',newline='') as c:
reader=csv.reader(c)
for row in reader:
print(row)
'''
['id', 'name', 'age']
['1', 'Mike', '20']
['2', 'Bob', '35']
'''
import pymongo
mongo_py=pymongo.MongoClient() #连接数据库
db=mongo_py["six"]
collection=db["stu"] #或collection=mongo_py["six"][stu]
#插入
collection.insert_one({"name":"张三"})
collection.insert_many({"name":"李四"},{"age":14})
#删除
collection.delete_one({"name":"张三"})
collection.delete_many({"name":"李四"})
#改
collection.update({"name":"李四"},{"$set":{{"age":15}}})
collection.update_many()
#查
collection.find_one({"age":15})
match_list=collection.find({"age":15})
for match in match_list:
print(match)
#关闭
mongo_py.close()
import redis
#1.链接数据库
client=redis.StrictRedis(host="127.0.0.1",port="6379",db=0)
#2.设置key
key="python"
#result 返回True或False
#3.string 增加
result=client.set(key,"1")
#4.删除
#result=client.delete(key)
#5.改
result=client.set(key,"舒服")
#6.查
result=client.get(key).decode() #不加decode返回二进制
print(result)
#7.查看所有键
result=client.keys() #可以加正则匹配
有时在用 requests 抓取页面的时候,得到的结果可能和浏览器中看到的不一样,这是因为requests获取到的都是原始的 HTML 文档,而浏览器中的页面则是 JS 处理数据后生成的结果,这些数据的来源有多种,可能是通过 Ajax 加载的,也可能是包含在 HTML 文档中,也可能是经过 JS 和特定算法计算后生成的
对于第一种情况,数据加载是一种异步加载方式,原始的页面最初不会包含某些数据,原始页面加载完后,会再向某个服务器请求某个接口获取数据,然后数据才被处理而呈现到网页上,这其实就是发送了一个 Ajax 请求,折中形式的页面在越来越多,如果可以用 requests来模拟 Ajax 请求,就可以成功抓取了
Ajax,异步的JS和XML,它不是编程语言,而是利用 JS在保证页面不被刷新,页面链接不改变的情况下与服务器交换数据并更新网页的技术。在浏览网页时有很多下滑查看更多的选项,这就是 Ajax 获取解析数据并呈现的过程。发送 Ajax 请求到网页更新的过程可以简单分为3步:发送请求,解析内容,渲染页面
JS 对 Ajax 最底层的实现,实际上就是新建了 XMLHttpRequest对象,然后调用 onreadystatechange 属性设置了监听,然后调用 open()和send()方法向某个链接(也就是服务器)发送了请求,前面由于用 Python 实现请求发送后,可以得到响应结果,但这里请求的发送变成 JS来完成。由于设置了监听,当服务器返回响应时,onreadystatechange对应的方法便会被触发,然后在这个方法里解析响应内容即可
得到响应之后,onreadystatechange属性对应的方法便会被触发,此时利用xmlhttp的responseText属性便可获取到响应内容,类似于Python中利用requests向服务器发起请求,然后得到响应的过程,返回内容可能是 HTML,也可能是 JSON,只需要在方法中用 JS 进一步处理即可,比如,如果是JSON的话,可以进行解析和转化
JS 有改变网页内容的能力,解析完响应内容后,可以调用 JS 对解析完的内容进行下一步处理了,通过 DOM 操作 进行增删改查即可。
在检查中的Network中找到Type为xhr,以getindex开头的选项,单击如下
在 Request Headers 中有一个信息名为X-Requested-With:XMLHttpRequest,这就标记了此请求是 Ajax请求,点击Preview即可看到响应内容,JS拿到这些数据后再执行相应的渲染方法,整个页面就渲染出来了
也可以切换到Response选项卡观察到真实的返回数据(Preview中的数据经过了Chrome的自动解析,如转码等)
在最开始的网页源代码中可以看到它非常简单,只是执行了一些 JS 代码,所以看到的真实数据其实都是Ajax发送请求后浏览器进一步渲染得到的。
观察一次次加载的请求链接
除第一个链接的containerid是100505+value(刚打开页面还未加载就出现)其他都是107603+value,继续加载能发现每一个新链接除since_id不一样外,其余都一样,因此可以构造since_id发送请求来爬取页面
字面意思可以理解到since_id标志着上一次加载到哪里,这一次从哪里开始加载,打开每一个链接的响应内容,可以看到返回内容为JSON
其中的内容便包含下一个链接的since_id,类似递推的关系,只要给出第一个链接,提取其相应内容,便可以构造出下一页的链接,从而爬取指定页面的内容
Ajax的分析和抓取其实也是 JS 动态渲染页面的一种情形,通过直接分析 Ajax ,我们仍然可以借助requests或urllib来实现数据抓取。不过 JS 动态渲染的页面不止 Ajax 这一种,有的网站分页部分是由 JS 生成的,并非原始的HTML代码,这其中不包含Ajax请求,还有淘宝页面,即使是用 Ajax 获取的数据,但是其 Ajax 接口有很多的加密参数,很难找出其规律,也很难直接分析 Ajax 来抓取
为了解决这些问题,我们可以直接使用模拟浏览器运行的方式来实现,这样就可以做到在浏览器中看到的是什么样,抓取的源码就是什么样,也就是可见即可爬。这样我们就不用再去管网页内部的 JS 代码用了什么算法渲染页面,不用管网页后台的 Ajax 接口到底有哪些参数
Python 提供了许多模拟浏览器运行的库,如Selenium,Splash,PyV8,Ghost等
Selenium是一个自动化测试工具,利用它可以驱动浏览器执行特定的动作,如点击,下拉等操作,同时还可以获取浏览器当前呈现的网页源代码,做到可见即可爬。对于一些 JS 动态渲染的页面来说,此种抓取方式非常有效
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
browser=webdriver.Chrome()
try:
browser.get('https://www.baidu.com')
inpu=browser.find_element_by_id('kw')
inpu.send_keys('Python')
inpu.send_keys(Keys.ENTER)
wait=WebDriverWait(browser,10)
wait.until(EC.presence_of_element_located((By.ID,'content_left')))
print(browser.current_url)
print(browser.get_cookies)
print(browser.page_source)
finally:
browser.close()
Selenium支持非常多浏览器,如Chrome,Firefox,Edge,还有 Android,BlackBerry等手机端的浏览器,另外也支持无界面浏览器PhantomJS
from selenium import webdriver
browser=webdriver.Chrome()
browser=webdriver.Firefox()
browser=webdriver.Edge()
browser=webdriver.PhantomJS()
browser=webdriver.Safari()
这样就完成了浏览器对象的初始化,并将其赋值为browser对象,接下来调用browser对象,让其执行各个动作以模拟浏览器操作
可以使用get()方法来请求网页,参数传入链接 URL 即可
from selenium import webdriver
browser=webdriver.Chrome()
browser.get('https://www.taobao.com')
print(browser.page_source)
browser.close()
Selenium 可以驱动浏览器完成各种操作,比如填充表单,模拟点击等。如果想要完成向某个输入框输入文字的操作,总要指定输入框在哪里,而Selenium提供了一系列查找节点的方法,可以根据这些方法获取想要的节点
观察网页源代码,淘宝网首页收缩框的id是q,name也是q。此外还有其他许多属性,此时就可以用多种方式获取它了
from selenium import webdriver
browser=webdriver.Chrome()
browser.get('https://www.taobao.com')
input_first=browser.find_element_by_id('q')
input_second=browser.find_element_by_css_selector('#q')
input_third=browser.find_element_by_xpath('//*[@id="q"]')
print(input_first,input_second,input_third)
#完全一样的
browser.close()
下面是所有获取单个节点的方法:
find_element_by_id
find_element_by_name
find_element_by_link_text
find_element_by_partial_link_text
find_element_by_tag_name
find_element_by_class_name
find_element_by_css_selector
另外还有通用方法 find_element(By,value) 比如 find_element_by_id(‘q’)等价于find_element(By.ID,‘q’)
要查找满足条件的所有结点要用find_elements()
Selenium可以驱动浏览器来执行一些操作,比较常见的用法有输入文字:send_keys(),清空文字 clear(),点击按钮click(),执行时先选中输入框节点,再输入,或者选中点击按钮,执行click()
更多操作
还有一些操作没有特定的执行对象,比如鼠标拖拽,键盘按键等,这些动作用另一种方式来执行,那就是动作链
比如实现一个节点的拖拽操作,将某个节点从一处拖拽到另一处,可以这样实现
from selenium import webdriver
from selenium.webdriver import ActionChains
#初始化浏览器对象
browser=webdriver.Chrome()
#打开网页
browser.get('http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')
#进入iframe
browser.switch_to_frame('iframeResult')
#选中被拖拽节点
source=browser.find_element_by_css_selector('#draggable')
#选中拖拽到的节点
target=browser.find_element_by_css_selector('#droppable')
#声明ActionChains对象并赋值为actions变量
actions=ActionChains(browser)
#调用方法
actions.drag_and_drop(source,target)
#执行
actions.perform()
更多操作
对于某些操作,Selenium API 并没有提供,比如下拉进度条,它可以直接模拟运行 JS,此时可以使用 execute_script()
from selenium import webdriver
from selenium.webdriver import ActionChains
browser=webdriver.Chrome()
browser.get('http://www.zhihu.com/explore')
browser.execute_script('window.scrollTo(0,document.body.scrollHeight)')
browser.execute_script('alert("TO Bottom")')
通过page_source属性可以获取网页源代码,解咒就可以使用解析库解析,不过 Selenium 提供了选择节点的方法,返回的是 WebElement 类型,那么它也有相关的方法和属性来直接获取节点信息,如属性,文本等。
选中节点后可以使用get_attribute(属性)来获取属性,获取文本可以选用节点的.text属性
网页中有一种节点叫 iframe,也就是子 Frame,相当于页面的子页面,它的结构和外部网页的结构完全一致,Selenium 打开网页后,它默认是在父级的 Frame 里操作,此时如果还有子 Frame,它是不能获取到子 Frame 里面的节点的,这就需要 switch_to.frame()方法来切换
在Selenium中,get()方法会在网页框架加载结束后结束执行,此时如果获取page_source,可能并不是浏览器完全加载完成的页面,如果某些网页有额外的 Ajax 请求,在网页源代码中也不一定能获取到,所有需要等待一定时间,确保节点已经加载完毕
隐式等待
隐式等待执行操作时,如果Selenium没有在DOM中找到节点,将继续等待,超出设定时间后,抛出找不到节点异常,默认事件是0
from selenium import webdriver
browser=webdriver.Chrome()
browser.implicitly_wait(10)
browser.get('https://www.zhihu.com/explore')
显示等待
隐式等待只是规定了一个固定时间,而页面的加载时间会受到网络条件的影响,还有一种更合适的显示等待方法,它指定要查找的节点,然后指定一个最长等待时间,如果在规定时间内加载了这个节点,就返回查找的节点,到规定时间未加载出节点,抛出超时异常
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
browser.get('https://www.baidu.com')
inpu=browser.find_element_by_id('kw')
inpu.send_keys('Python')
inpu.send_keys(Keys.ENTER)
#引入WebDriverWait对象,指定最长等待时间
wait=WebDriverWait(browser,10)
#调用方法指定加载确定节点
wait.until(EC.presence_of_element_located((By.ID,'content_left')))
#对于按钮可以更改等待条件EC.element_to_dlidkable(是否可点击)
其他等待条件
使用browser.back()和browser.forward()可以返回和前进页面
使用Selenium,还可以方便地对 Cookies 进行操作,如获取,添加,删除 Cookies 等
from selenium import webdriver
browser=webdriver.Chrome()
browser.implicitly_wait(10)
browser.get('https://www.zhihu.com/explore')
print(browser.get_cookies())
browser.add_cookie({'name':'name','domain':'www.zhihu.com','value':'germey'})
print(browser.get_cookies())
browser.delete_all_cookies()
print(browser.get_cookies())
在访问网页时,有时会开启一个选项卡,在Selenium中,我们也可以对选项卡进行操作
import time
from selenium import webdriver
browser=webdriver.Chrome()
browser.get('https://www.baidu.com')
#新开启一个选项卡
browser.execute_script('window.open()')
#获取当前开启的所有选项卡
print(browser.window_handles)
#跳转选项卡
browser.switch_to_window(browser.window_handles[1])
browser.get('https://www.taobao.com')
time.sleep(1)
browser.switch_to_window(browser.window_handles[0])
browser.get('https://python.org')
在使用 Selenium 的过程中,难免遇到一些异常,例如超时,节点未找到等错误,一旦出现此类错误,程序便不会自动运行,可以使用try except 语句来捕获异常
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
import os
import time
import json
class initial():
def browser_initial(self):
#浏览器初始化
browser=webdriver.Chrome()
log_url='https://login.taobao.com/member/login.jhtml?redirectURL=http%3A%2F%2Fs.taobao.com%2Fsearch%3Fq%3Dipad%26imgfile%3D%26commend%3Dall%26ssid%3Ds5-e%26search_type%3Ditem%26sourceId%3Dtb.index%26spm%3Da21bo.2017.201856-taobao-item.1%26ie%3Dutf8%26initiative_id%3Dtbindexz_20170306&uuid=9ebb031412159b09770b9acf3cdbde3a'
return log_url,browser
def get_cookies(self,log_url,browser):
#截获cookies并保存本地
browser.get(log_url)
time.sleep(15) #进行扫码
dictCookies=browser.get_cookies()
jsonCookies=json.dumps(dictCookies)
with open('Cookies.txt','w',encoding='utf-8') as f:
f.write(jsonCookies)
print('cookies保存成功')
def start(self):
tur=self.browser_initial()
self.get_cookies(tur[0],tur[1])
class Spider():
def browser_initial(self):
browser=webdriver.Chrome()
goal_url='https://s.taobao.com/search?q=ipad&bcoffset=7&p4ppushleft=2%2C48&ntoffset=7&s=0'
browser.get('https://login.taobao.com/member/login.jhtml?redirectURL=http%3A%2F%2Fs.taobao.com%2Fsearch%3Fq%3Dipad%26imgfile%3D%26commend%3Dall%26ssid%3Ds5-e%26search_type%3Ditem%26sourceId%3Dtb.index%26spm%3Da21bo.2017.201856-taobao-item.1%26ie%3Dutf8%26initiative_id%3Dtbindexz_20170306&uuid=9ebb031412159b09770b9acf3cdbde3a')
return goal_url,browser
def log_taobao(self,goal_url,browser):
#读取本地cookies并登录目标网页
with open('Cookies.txt','r',encoding='utf-8') as f:
listCookies=json.loads(f.read())
print('已经获取cookie')
for cookie in listCookies:
cookie_dict={
'domain':'.taobao.com',
'name':cookie.get('name'),
'value':cookie.get('value'),
'path':'/',
'expire':cookie.get('expire'),
"httpOnly":cookie.get("httpOnly"),
'secure':cookie.get('secure'),
}
browser.add_cookie(cookie_dict)
browser.get(goal_url)
return browser
def get_data(self,browser,page):
print('get_data这里',browser)
wait=WebDriverWait(browser,20)
if page>1:
inp=wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'#mainsrp-pager > div > div > div > div.form > input')))
submit=wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,'#mainsrp-pager > div > div > div > div.form > span.btn.J_Submit')))
inp.clear()
inp.send_keys(page)
submit.click()
#判断当前高亮页数是否为需求页数
wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR,'#mainsrp-pager > div > div > div > ul > li.item.active > span'),str(page)))
#判断商品是否加载完毕
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'body')))
html=browser.page_source
print(html.decode('utf-8'))
def start(self,page):
tur=self.browser_initial()
browser=self.log_taobao(tur[0],tur[1])
print('start这里',browser)
self.get_data(browser,page)
initial().start()
Spider().start(6)
Splash 是一个 JS 渲染服务,一个带有HTTP API 的轻量级浏览器,同时对接了Python 中的 Twisited 和 QT库,利用它可以实现动态渲染页面的抓取
Splash可以通过 Lua 脚本执行一系列渲染操作,这样就可以用Splash来模拟 Chrome的操作
入口及返回值
#方法名必须是main(),Splash会默认调用这个方法
function main(splash,args)
splash:go('http://www.baidu.com')
splash:wait(0.5)
lacal title=splash:evaljs('document.title')
return {title=title}
end
#返回网页标题
异步处理
Splash支持异步处理,但是并没有显示指明回调方法,其回调的跳转是在Splash内部完成的
function main(splash,args)
local example_urls={'www.baidu.com','www.taobao.com','www.zhihu.com'}
local urls= args.urls or example_urls
local result={}
for index,url in ipairs(urls) do
#字符串拼接是用 .. 而不是 +
local ok,reason=splash:go('http://'..url)
if ok then
#当Splash执行到wait时会去处理其他任务,指定时间过后再回来继续处理
splash:wait(2)
results[url]=splash:png()
end
end
return results
end
main()方法的第一个参数是splash,这个对象相当于Selenium中的WebDriver对象,可以调用它的一些属性和方法来控制加载过程
下面是Splash对象的一些属性
args
该属性可以获取加载时配置的属性,比如URL,如果为GET请求,可以获得GET请求的参数;如果为POST请求,可以获取表单提交的数据
Splash也支持似乎用第二个参数直接作为args
function main(splash,args)
local url=args.url
end
第二个参数args就相当于splash.args属性
js_enabled
这个属性是Splash的 JS 执行开关,默认为false
resource_timeout
设置加载的超时时间,设置为 0 或 nil (类似于Python中的None),代表不检测超时
images_enabled
设置图片是否加载,默认情况是加载的,禁用后可以节省网络流量并提高网页加载速度,但可能会影响 JS 渲染,因为禁用图片后,它外层的DOM节点高度会受影响,进而影响DOM节点的位置,如果 JS 对图片节点有操作的话,执行就会收到影响。
另外,Splash 使用了缓存,如果一开始加载了网页图片,之后禁用,再加载网页,之前加载好的网页可能还会显示,这时直接重启Splash即可
plugins_enabled
此属性可以控制浏览器插件是否开启,默认为false
scroll_position
设置此属性可以控制页面上下或左右滚动
function main(splash,args)
assert(splash:go('https://www.taobao.com'))
splash.scroll_position={y=400}
return {png=splash:png()}
end
splash.scroll_position={x=100,y=200}
请求某个链接,可以模拟GET和POST请求,同时支持传入请求头,表单等数据
ok,reason = splash:go {url,baseurl=nil,headers=nil,http_method=‘GET’,body=nil,formdata=nil}
参数说明
baseurl:表示资源加载的相对路径
body:发送 POST 请求时的表单数据,使用的Content-type为application/json
foromdata:发送 POST 请求时的表单数据,使用的Content-type为application/x-www-form-urlencoded
该方法返回的结果是结果 ok 和原因 reason 的组合,如果 ok 为空,代表网页的加载出现了错误,此时 reason 变量中包含了错误原因,否则页面加载成功
function main(splash,args)
local ok,reason=splash:go("http://httpbin.org/post",http_method='POST',body="name=YS")
if ok then
return splash:html()
end
end
控制页面的等待时间
ok,reason = splash:wait{time,cancel_on_redirect=false,cancen_on_error=true}
参数说明
cancel_on_redirect:可选参数,默认为false,表示如果发生了重定向就停止等待并返回重定向结果
cancel_on_error:可选参数,默认为false,表示如果发生了加载错误就停止等待
返回结果同样是结果 ok 和原因 reason 的组合
此方法可以直接调用 JS 定义的方法,但是所调用的方法需要用双中括号包围,相当于实现了 JS 方法到 Lua 脚本的转化
function main(splash,args)
local get_div_count = splash:jsfunc([[
function(){
var body = document.body;
var divs = body.getElementByTagName('div');
return divs.length
}
]])
splash:go('https://www.baidu.com')
return ('There are %S DIVS'):format(get_div_count())
此方法可以执行 JS 代码并返回最后一条 JS 语句的返回结果
result = splash:evaljs(js)
比如可以用 local title = splash:evaljs(‘document.title’) 来获取页面标题
此方法可以执行 JS 代码,与 evaljs 类似,但更偏向于执行某些特定动作或声明某些方法
function main(splash,args)
splash:go('http://www.baidu.com')
splash:runjs("foo=function(){reyirm 'bar'}")
local result = splash:evaljs('foo()')
return result
end
此方法可设置每个页面访问时自动加载的对象
ok,reason = splash:autload(source_or_url,source=nil,url=nil)
参数说明
source_or_url:JS 代码或 JS 库链接
source:JS 代码
url:JS 库链接
此方法只负责加载 JS 代码或库,不执行任何操作,如果要执行操作可以调用 evaljs()和runjs()
function main(splash,args)
splash:autoload([[
function get_document_title(){
return document.title;
}
]])
splash:go('https://www.baidu.com')
return splash:evaljs('get_document_title()')
end
另外也可以加载某些方法库,如 jQuery
function main(splash,args)
assert(splash:autoload('https://code.jquery.com/jquert-2.1.3.min.js'))
assert(splash:go('https://www.taobao.com'))
local version = splash:evaljs('$.fn.jquery')
return 'JQuery version:'.. version
此方法可以通过设置定时任务和延时时间来实现任务延长执行,并且可以再执行前通过 cancel() 方法重新执行定时任务
function main(splash,args)
local snapshots={}
local timer=splash:call_later(function()
snapshots['a'] = splash:png()
splash:wait(1.0)
snapshots['b'] = splash:png()
end,0.2)
splash:go('https://www.taobao.com')
splash:wait(3.0)
return snapshots
end
此方法可以模拟发送 HTTP 的 GET 请求
response = splash:http_get{url,headers=nil,follow_redirects = true}
follow_redirects:可选参数,表示是否启动自动重定向
response = splash:http_post{url,headers=nil,follow_redirects = true,body=nil}
body:可选参数,即表单数据,默认为空
此方法用来设置页面的内容
function main(splash)
assert(splash:set_content('hello
'))
end
用来获取网页源代码
function main(splash,args)
splash:go('https://httpbin.org/get')
return splash:html()
end
此方法用来获取PNG格式的网页截图
用来获取JPEG格式的网页截图
用来获取页面加载的过程描述
获取当前正在访问的 URL
获取当前页面的Cookies
为当前页面添加cookie
此方法可以清除所有的Cookies
获取当前浏览器页面的大小,即宽高
设置当前浏览器页面的大小,即宽高
设置浏览器全屏展示
设置浏览器的User-Agent
设置请求头
function main(splash)
splash:set_custom_headers({
['User-Agent']='Splash'
['Site']='Splash'
})
splash:go('http://httpbin.org/get')
return splash.html()
end
该方法可以选中符合条件的第一个节点,如果有多个节点符合条件,只返回一个,参数为CSS选择器
function main(splash)
splash:go('https://www.baidu.com')
input=splash:select('#kw')
input:send_text('Splash')
splash.wait(3)
return splash:png()
end
选中符合条件的所有节点
function main(splash)
local treat = require('treat')
assert(splash:go('http://quotes.toscrape.com/'))
assert(splash:wait(0.5))
local texts = splash:select_all('.quote.text')
local results = {}
for index,text in ipairs(tetxs) do
results[index] = text.node.innerHTML
end
return treat.as_array(results)
end
此方法可以模拟鼠标点击操作,传入的参数为坐标值 x 和 y ,此外也可以直接选中某个节点,然后调用此方法
function main(splash)
splash:go('https://www.baidu.com/')
input = splash:select('#kw')
input:send_text('Splash')
submit = splash:select('#su')
submin:mouse_click()
splash:wait(3)
return splash:png()
end
前面说明了 Splash Lua 脚本的用法,Splash 给我们提供了一些 HTTP API 接口,我们只需要传递相应参数即可
此接口用于获取 JS 渲染的页面的 HTML 代码,接口地址为 Splash 的运行地址加此接口名称,给此接口传递 url 参数来指定渲染的 URL ,返回结果即是渲染后的源代码
import requests
url = 'http://localhost:8050/render.html?url=https://www.baidu.com&wait=5'
response=requests.get(url)
print(response.text)
另外,此接口还支持代理设置,图片加载设置,Headers 设置,请求方法设置
返回页面截图的 PNG 格式二进制数据,需要的参数比render.html多了几个,如需要设置页面宽高
返回页面截图的 JPEG 格式二进制数据,比 render.png 多了 quality 参数,用来设置图片质量
返回页面加载的 HAR 数据,是一个 JSON 格式的数据
此接口包含了前面接口的所有功能,返回结果是 JSON 格式
可以传入不同的参数控制其返回结果,比如传入 html = 1,返回结果增加网页源代码数据,传入 png = 1,返回数据增加了页面的 PNG 截图数据
此接口是最强大的接口,可以实现 Lua 脚本的对接,可以于网页交互
import requests
from urllib.parse import quote
lua = '''
function main(splash)
return 'hello'
end
'''
url = 'http://localhost:8050/execute?lua_source='+quote(lua)
response = requests.get(url)
print(response.text)
#hello
Lua 脚本用三引号包括起来,quote()将脚本进行 URL 转码,构造 Splash请求 URL,将其作为lua_source 参数传递,这样运行结果会显示 Lua 脚本执行后的结果
import requests
from urllib.parse import quote
lua = '''
function main(splash)
local treat = require('treat')
local response = splash:http_get('http://httpbin.org/get')
return {
html = treat.as_string(response.body),
url = response.url,
status = response.status
}
end
'''
url = 'http://localhost:8050/execute?lua_source='+quote(lua)
response = requests.get(url)
print(response.text)
'''
{"html": "{\n \"args\": {}, \n \"headers\": {\n \"Accept-Encoding\": \"gzip, deflate\", \n \"Accept-Language\": \"en,*\", \n \"Host\": \"httpbin.org\", \n \"User-Agent\": \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/602.1 (KHTML, like Gecko) splash Version/10.0 Safari/602.1\", \n \"X-Amzn-Trace-Id\": \"Root=1-602a82d9-7b311490670a6a0521213ed7\"\n }, \n \"origin\": \"183.18244\", \n \"url\": \"http://httpbin.org/get\"\n}\n", "status": 200, "url": "http://httpbin.org/get"}
'''
如此一来,之前的 Lua 脚本均可用此方法与 Python 进行对接,所有网页的动态渲染,模拟点击,表单提交,页面滑动,延时等待均可自由控制
在用 Splash 做页面抓取时,如果爬取的量非常大,用一个 Splash 服务来处理压力太大,此时可以考虑搭建一个负载均衡器来把压力分散到各个服务器上,相当于多个机器多个服务共同参与任务的处理,可以减小单个 Splash 服务的压力
import pytesseract
from PIL import Image
Image = Image.open('C:/Users/PC/Desktop/CheckCode.jpg')
Image = Image.convert('L')
r = pytesseract.image_to_string(Image)
Image.show() #看到处理之后的图片
print(r) #kYnS
识别率较低,需要经过训练
极验滑动验证码需要拖动拼合滑块才能完成验证,可以使用 Selenium 来实现
极验验证码官网为 http://www.geetest.com/
极验验证码较图形验证码识别难度更大,有一种情况为先点击按钮进行智能验证,如果验证不通过,则会弹出滑动验证的窗口,拖动滑块拼合图像进行验证,之后三个加密参数会生成,通过表单提交到后台,后台还会进行一次验证
识别思路
对于智能验证按钮,一般同一个会话第二次点击会直接通过验证,如果智能识别不通过,会弹出滑动验证窗口,需要拖动滑块完成二步验证,接下来就可以提交表单了
模拟点击可以用 Selenium 直接实现,滑动拼接待学 dog~
点触验证码也是一种应用广泛的验证码,同样可以使用 Selenium 实现,但识别难度也较大,可以通过网络平台提交识别 dog~
from urllib.error import URLError
from urllib.request import ProxyHandler,build_opener
import requests
proxy='185.38.111.1:8080'
#需要认证在代练前加上用户名和密码
#proxy='username:[email protected]:8080'
proxies={
'http':'http://'+proxy,
'https':'https://'+proxy
}
try:
response=requests.get('http://httpbin.org/get',proxies=proxies,timeout=10)
print(response.content.decode('utf-8'))
except:
print('出错')
代理需要提前筛选,将不可用的剔除掉,保留可用代理,
class MatplotlibExamplesItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
file_urls=scrapy.Field()
files=scrapy.Field()
修改FilesPipeline的文件命名规则,实现文件内容命名,同时分在不同文件夹
from scrapy.pipelines.files import FilesPipeline
from urllib.parse import urlparse
from os.path import basename,dirname,join
class MyFilesPipeline(FilesPipeline):
def file_path(self, request, response=None, info=None):
path = urlparse(request.url).path #eg_path=axes_grid//demo_axes_grid.py
return join(basename(dirname(path)),basename(path))#以路径目录创建文件夹下载
import scrapy
import re
from scrapy.pipelines.images import ImagesPipeline
from scrapy.exceptions import DropItem
from os.path import join
class NewSoimagesPipeline:
def process_item(self, item, spider):
return item
class MyFilesPipeline(ImagesPipeline):
def get_media_requests(self,item,info):
image_url=item['image_url']
yield scrapy.Request(image_url,meta={'name':item['image_name']})
'''def item_completed(self,results,item,info):
image_paths=[x['path'] for ok,x in results if ok]
print('item_completed已经被执行')
if not image_paths:
raise DropItem('Item contains no images')
return item'''
def file_path(self,request,response=None,info=None):
name=request.meta['name']
filename=name+'.jpg'
return join('C:\\Users\\PC\\Desktop\\Python\\爬虫练习\\new_soimages\\new_soimages\\images',filename)
ITEM_PIPELINES = {
'(#projectname).pipelines.MyFilesPipeline':1
}
FILES_STORE='examples_src'
#IMAGES_STORE='image'
import scrapy
from scrapy.linkextractors import LinkExtractor
from ..items import MatplotlibExamplesItem
class ExamplesSpider(scrapy.Spider):
name = 'examples'
#allowed_domains = ['matplotlib.org']
start_urls = ['http://matplotlib.org/examples/index.html']
#提取详情页面链接并提交
def parse(self, response):
le=LinkExtractor(restrict_xpaths='//li[@class="toctree-l1"]/ul/li[@class="toctree-l2"]')
links=le.extract_links(response)
for link in links:
yield scrapy.Request(link.url,callback=self.parse_examples)
#提取下载url并提交
def parse_examples(self,response):
href=response.xpath('//a[@class="reference external"]/@href').extract_first()
url=response.urljoin(href)
example=MatplotlibExamplesItem()
example['file_urls']=[url]
return example
import scrapy
import json
from ..items import NewSoimagesItem
class ImageSpider(scrapy.Spider):
name = 'image'
#allowed_domains = ['none']
BASE_URL = 'https://image.so.com/zjl?ch=art&sn={}&listtype=new&temp=1'
start_index = 0
MAX_DOWNLOAD_NUM = 100 #下载数量约为 (MAX_DOWNLOAD_NUM/一页的数量+1)*一页的数量
start_urls = [BASE_URL.format(0)]
def parse(self, response):
infos=json.loads(response.body.decode('utf-8'))
#print(infos)
item=NewSoimagesItem()
for info in infos['list']:
item['image_url']=info['qhimg_url']
item['image_name']=info['pic_desc']
yield item
self.start_index=self.start_index+infos['count']
if infos['count']>0 and self.start_index<self.MAX_DOWNLOAD_NUM:
yield scrapy.Request(self.BASE_URL.format(self.start_index),callback=self.parse)
import scrapy
from scrapy.http import Request,FormRequest
class LoginSpider(scrapy.Spider):
name='login'
allowed_domains='www.yaozh.com'
start_urls=['https://www.yaozh.com']
login_url='https://www.yaozh.com/login/'
def parse(self,response):
name=response.xpath('/html/body/div[1]/div/div[2]/a[1]/text()')
yield name
def start_reqeusts(self):
yield Request(self.login_url,callback=self.login)
def login(self,response):
fd={'username':'15525810502','password':'86vlwug1'}
yield FormRequest.from_response(response,formdata=fd,callback=self.parse_login)
def parse_login(self,response):
if 'desktop landscape js rgba borderimage borderradius boxshadow textshadow opacity placeholder no-iframe' in response.text:
print('登录成功')
yield from super().start_requests()
else:
print('登录失败')
import re
import scrapy
class LoginTestSpider(scrapy.Spider):
name = 'login_test'
allowed_domains = ['www.yaozh.com']
start_urls = ['https://www.yaozh.com/member/']
def start_requests(self):
cookies='think_language=zh-CN; Hm_lvt_65968db3ac154c3089d7f9a4cbb98c94=1612775691; _ga=GA1.2.87747901612775691; _gid=GA1.2.1940605412.1612775691; _gat=1; Hm_lpvt_65968db3ac154c3089d7f9a4cbb98c94=1612778959; yaozh_userId=1034779; UtzD_f52b_saltkey=r8rUC8oU; UtzD_f52b_lastvisit=1612775378; yaozh_uidhas=1; acw_tc=2f624a2c16127789259967583e05f17b4777229366283295014080103242da; UtzD_f52b_ulastactivity=1611138183%7C0; _ga=GA1.1.1686181705.1612778990; _gid=GA1.1.431866152.1612778990; yaozh_logintime=1612840644; yaozh_user=1034779%09YS12345; yaozh_jobstatus=kptta67UcJieW6zKnFSe2JyYnoaSZ5hpnJueg26qb21rg66flM6bh5%2BscZhyVLy4k5OXmJZZoKifnZ%2BDn5iorJDVop6Yg3HYnmpnm1pjmZa00Ae2c26e315ea124717CC60390509fFUmZebmWueV6DXn5VtWamhnsZbbKabZ5ieW2iXaWSbmZKXmJmUZ5VXoOE%3Df83eeaa91a70f1c66e3f4e55f8c3e222; db_w_auth=851571%09YS12345; UtzD_f52b_lastact=1612840646%09uc.php%09; UtzD_f52b_auth=b2bcvD2LRsEV6wnPyCO3tguMVjskJED3%2FO2sykkP9mUai1qU9Cc%2F2sFM1ui3OdD62G2Z6pMkSelfcK5INGEu8EIAkWc; yaozh_mylogin=1612840648; PHPSESSID=n10239pmii120ncrdkdgtq0a85'
cookie_list=[cookie for cookie in cookies.split(';')]
#print(cookie_list)
try:
cookie_list.remove('')
except:
pass
cookies={cookie.split('=')[0]:cookie.split('=')[1] for cookie in cookie_list}
#print(cookies)
yield scrapy.Request(self.start_urls[0],cookies=cookies,callback=self.parse)
def parse(self, response):
print('开始查找')
p=re.compile('0.00')
print(p.findall(response.body.decode('utf-8')))
import scrapy
from scrapy_splash import SplashRequest
class QuotesSpider(scrapy.Spider):
name = 'quotes'
#allowed_domains = ['quotes.toscrape.com']
start_urls = ['http://quotes.toscrape.com/js']
def start_requests(self):
#请求第一页
for url in self.start_urls:
#调用SplashRequest渲染
yield SplashRequest(url,args={'images':0,'timeout':3})
def parse(self, response):
#获取名言和作者
for sel in response.xpath('//div[@class="quote"]'):
quote=sel.xpath('./span[@class="text"]/text()').extract_first()
author=sel.xpath('./span/small[@class="author"]/text()').extract_first()
yield {'quote':quote,'author':author}
#获取下一页路径
href=response.xpath('//li[@class="next"]/a/@href').extract_first()
if href:
url=response.urljoin(href)
yield SplashRequest(url,args={'image':0,'timeout':3},callback=self.parse)
#设置渲染服务地址
SPLASH_URL='http://localhost:8050'
#设置去重过滤
DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
#用来支持cache_args(用于缓存每次都需要设置的args属性),
SPIDER_MIDDLEWARES = {
#'spalsh_example.middlewares.SpalshExampleSpiderMiddleware': 543,
'scrapy_splash.SplashDeduplicateArgsMiddleware':100,
}
DOWNLOADER_MIDDLEWARES = {
#'spalsh_example.middlewares.SpalshExampleDownloaderMiddleware': 543,
'scrapy_splash.SplashCookiesMiddleware':723,
'scrapy_splash.SplashMiddleware':725,
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware':810
}
import scrapy
from scrapy import Request
from scrapy_splash import SplashRequest
import json
# lua语言
lua_script='''
--定义一个函数,参数是splash
function main(splash)
-- 打开url页面,这里指打开 splash.args.url 就相当于加载时配置的URL参数(即SPLASH_URL = 'http://localhost:8050')
splash:go(splash.args.url)
-- 等待页面渲染2秒
splash:wait(2)
-- 执行JavaScript语句(无返回值)滚动浏览器到访问 HTML 元素中的 "calass=page",意思是滚动浏览器到底端(page位置)。
splash:runjs("document.getElementsByClassName('page')[0].scrollIntoView(true)")
-- 等待页面渲染2秒
splash:wait(2)
-- 返回获取当前页面的 html 文本
return splash:html()
end
'''
class JdBookSpider(scrapy.Spider):
name = 'jd_book'
allowed_domains = ['search.jd.com']
# start_urls = ['http://search.jd.com/']
base_url = 'https://search.jd.com/Search?keyword=python&enc=utf-8&wq=python' # 起始页
def start_requests(self):
# 请求第一页, 无需 JS 渲染
yield Request(self.base_url, callback=self.parse_urls, dont_filter=True)
def parse_urls(self,response):
# 获取商品总数,计算出总页数
#total = int(response.css('span#J_resCount::text').extract_first())
#total = response.css('span#J_resCount::text').re_first('\d+.+\d')
#total = int(float(total)*10000)
#pageNum = total // 60 + (1 if total % 60 else 0 )
# 测试时可以不用取那么多页
pageNum = 4
# 构造每页的url,向 Splash 的 execute 端点发送请求
for i in range(pageNum):
url = '%s&page=%s' % (self.base_url, 2 * i + 1)
# print(url)
yield SplashRequest(url,
endpoint='execute',
args={'lua_source':lua_script},
cache_args=['lua_source'])
def parse(self, response):
# 获取每一个页面中每本书的名字和价格
for sel in response.css('ul.gl-warp.clearfix > li.gl-item'):
yield {
'name':sel.css('div.p-name').xpath('string(.//em)').extract_first(),
'pric':sel.css('div.p-price i::text').extract_first(),
}
SPLASH_URL = 'http://localhost:8050'
USER_AGENT='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36'
SPIDER_MIDDLEWARES = {
'jd.middlewares.JdSpiderMiddleware': 543,
'scrapy_splash.SplashDeduplicateArgsMiddleware':100,
}
DOWNLOADER_MIDDLEWARES = {
'jd.middlewares.JdDownloaderMiddleware': 543,
'scrapy_splash.SplashCookiesMiddleware':723,
'scrapy_splash.SplashMiddleware':725,
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware':810
}
https://movie.douban.com/j/search_subjects?type=movie&tag=%E8%B1%86%E7%93%A3%E9%AB%98%E5%88%86&sort=recommend&page_limit=20&page_start=0
https://movie.douban.com/j/search_subjects?type=movie&tag=%E8%B1%86%E7%93%A3%E9%AB%98%E5%88%86&sort=recommend&page_limit=20&page_start=20
USER_AGENT='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36'
ROBOTSTXT_OBEY = False
DOWNLOAD_DELAY = 3
DOWNLOADER_MIDDLEWARES = {
'movie.middlewares.MovieDownloaderMiddleware': 543,
}
class BookItem(scrapy.Item):
# 使用Field()元数据封装数据
name = scrapy.Field() # 书名
price = scrapy.Field() # 价格
review_rating = scrapy.Field() # 评价等级(1~5 星)
review_num = scrapy.Field() # 评价数量
upc = scrapy.Field() # 产品编码
stock = scrapy.Field() # 库存数量
# 当存储在 mongodb 数据库中时,需要加上这么一句
_id = scrapy.Field()
#实现数据写入到 MongoDB 数据库中
# 在 pipleines.py 中实现 MongoDBPipeline 代码如下:
from pymongo import MongoClient
class MongoDBPipeline:
# open_spider 方法在开始爬取数据之前被调用,在该方法中通过spider.settings 对象读取用户在配置文件中的指定的数据库,
# 然后建立与数据库连接,将得到的 MongoClient 对象和 Database 对象,分别赋值给 self.db_client 和 self.db 以便后用
def open_spider(self,spider):
db_uri = spider.settings.get('MONGODB_URI','mongodb://localhost:27017')
db_name = spider.settings.get('MONGODB_DB_NAME','scrapy_default')
self.db_client = MongoClient('mongodb://localhost:27017')
self.db = self.db_client[db_name]
# close_spider 方法,爬取数据后,关闭连接
def close_spider(self,spider):
self.db_client.close()
# process_item 方法处理爬取到的每一项数据,在该方法中调用 insert_db 方法,执行数据库的插入操作。
# 在 insert_db 方法中,先将一项数据转成字典,然后调用 insert_one 方法将其插入集合 books。
def process_item(self,item,spider):
self.insert_db(item)
return item
def insert_db(self,item):
# isinstance 类型判断函数 (判断 item 是否是item对象类型)
if isinstance(item,type(item)):
item = dict(item)
self.db.books.insert_one(item)
from scrapy.exceptions import DropItem
class DuplicatesPipline(object):
def __init__(self):
self.book_set = set()
def process_item(self, item, spider):
# 取出name
name = item['name']
# 判断name是否在book_set中
if name in self.book_set:
raise DropItem("Duplicat book found: %s" % item)
else:
self.book_set.add(name)
return item
# Mongodb 数据库信息设置 2020-03-24
MONGODB_URI = 'mongodb://localhost:27017'
MONGODB_DB_NAME = 'scrapy_db'
ITEM_PIPELINES = {
#'toscrape_book.pipelines.ToscrapeBookPipeline': 300,
'toscrape_book.pipelines.BookPipeline': 310, #数字转化
'toscrape_book.pipelines.DuplicatesPipline': 300, #去重
# 把数据存储在 mongodb 数据库中 2020-03-24
'toscrape_book.pipelines.MongoDBPipeline':403,
}
class BookItem(scrapy.Item):
# 使用Field()元数据封装数据
name = scrapy.Field() # 书名
price = scrapy.Field() # 价格
review_rating = scrapy.Field() # 评价等级(1~5 星)
review_num = scrapy.Field() # 评价数量
upc = scrapy.Field() # 产品编码
stock = scrapy.Field() # 库存数量
ITEM_PIPELINES = {
#'toscrape_book.pipelines.ToscrapeBookPipeline': 300,
'toscrape_book.pipelines.BookPipeline': 310, #数字转化
'toscrape_book.pipelines.DuplicatesPipline': 300, #去重
'toscrape_book.pipelines.RedisPipeline':404,
}
# 以excel格式导出的Exporter
from scrapy.exporters import BaseItemExporter
import xlwt
class ExcelItemExporter(BaseItemExporter):
def __init__(self, file, **kwargs):
self._configure(kwargs)
self.file = file
self.wbook = xlwt.Workbook()
self.wsheet = self.wbook.add_sheet('scrapy')
self.row = 0
def finish_exporting(self):
self.wbook.save(self.file)
def export_item(self, item):
fields = self._get_serialized_fields(item)
for col, v in enumerate(x for _,x in fields):
self.wsheet.write(self.row,col,v)
self.row += 1
# 添加新的导出数据格式
FEED_EXPORTERS = {'excel':'toscrape_book.my_exporters.ExcelItemExporter'}
import scrapy
from scrapy.linkextractors import LinkExtractor
from ..items import BookItem
class BooksSpider(scrapy.Spider):
# 爬虫名
name = 'books'
allowed_domains = ['books.toscrape.com']
# 抓取网点起点
start_urls = ['http://books.toscrape.com/']
# 书籍列表页面的解析函数
def parse(self, response):
# 提取书籍列表页面中每本书的链接 (LinkExtractor提取链接的用法)
le = LinkExtractor(restrict_css='article.product_pod h3')
links = le.extract_links(response)
# print('links_list_url:',links)
for link in links : # le.extract_links(response):
yield scrapy.Request(link.url, callback=self.parse_book)
# 提取下一页(LinkExtractor提取链接的用法)
le = LinkExtractor(restrict_css='ul.pager li.next')
links = le.extract_links(response)
# print('links_next_url:',links)
if links:
next_url = links[0].url
yield scrapy.Request(next_url, callback=self.parse)
#书籍页面的解析函数:
def parse_book(self,response):
# 元数据保存先在item.py定义,再引用
book = BookItem()
sel = response.css('div.product_main')
book['name'] = sel.css('h1::text').extract_first()
book['price'] = sel.css('p.price_color::text').extract_first()
book['review_rating'] = sel.css('p.star-rating::attr(class)').re_first('star-rating ([A-Za-z]+)')
book['stock'] = sel.css('p.instock::text').re_first('stock \((.*?) available\)')
sel = response.css('table.table-striped')
book['upc'] = sel.css('tr:nth-child(1)>td::text').extract_first()
book['review_num'] = sel.css('tr:nth-last-child(1)>td::text').extract_first()
yield book
xporter):
def init(self, file, **kwargs):
self._configure(kwargs)
self.file = file
self.wbook = xlwt.Workbook()
self.wsheet = self.wbook.add_sheet(‘scrapy’)
self.row = 0
def finish_exporting(self):
self.wbook.save(self.file)
def export_item(self, item):
fields = self._get_serialized_fields(item)
for col, v in enumerate(x for _,x in fields):
self.wsheet.write(self.row,col,v)
self.row += 1
#### 4.3.2.启动配置
```python
# 添加新的导出数据格式
FEED_EXPORTERS = {'excel':'toscrape_book.my_exporters.ExcelItemExporter'}
import scrapy
from scrapy.linkextractors import LinkExtractor
from ..items import BookItem
class BooksSpider(scrapy.Spider):
# 爬虫名
name = 'books'
allowed_domains = ['books.toscrape.com']
# 抓取网点起点
start_urls = ['http://books.toscrape.com/']
# 书籍列表页面的解析函数
def parse(self, response):
# 提取书籍列表页面中每本书的链接 (LinkExtractor提取链接的用法)
le = LinkExtractor(restrict_css='article.product_pod h3')
links = le.extract_links(response)
# print('links_list_url:',links)
for link in links : # le.extract_links(response):
yield scrapy.Request(link.url, callback=self.parse_book)
# 提取下一页(LinkExtractor提取链接的用法)
le = LinkExtractor(restrict_css='ul.pager li.next')
links = le.extract_links(response)
# print('links_next_url:',links)
if links:
next_url = links[0].url
yield scrapy.Request(next_url, callback=self.parse)
#书籍页面的解析函数:
def parse_book(self,response):
# 元数据保存先在item.py定义,再引用
book = BookItem()
sel = response.css('div.product_main')
book['name'] = sel.css('h1::text').extract_first()
book['price'] = sel.css('p.price_color::text').extract_first()
book['review_rating'] = sel.css('p.star-rating::attr(class)').re_first('star-rating ([A-Za-z]+)')
book['stock'] = sel.css('p.instock::text').re_first('stock \((.*?) available\)')
sel = response.css('table.table-striped')
book['upc'] = sel.css('tr:nth-child(1)>td::text').extract_first()
book['review_num'] = sel.css('tr:nth-last-child(1)>td::text').extract_first()
yield book