此处推荐使用国内源Anaconda下载安装使用,可以参考下面的文章:
一、安装环境
sudo pip3 install virtualenv -i https://pypi.douban.com/simple/
二、安装virtualenvwrapper
sudo pip3 install virtualenvwrapper -i https://pypi.douban.com/simple/
三、配置
export WORKON_HOME=/home/ljh/.virtualenvs export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3.5 source /usr/local/bin/virtualenvwrapper.sh
四、创建虚拟环境
mkvirtualenv testlev
五、切换虚拟环境
workon testlev
六、关闭虚拟环境
deactivate testlev
七、删除虚拟环境
rmvirtualenv testlev
什么是爬虫?
网络爬虫(又被称为网页蜘蛛,网络机器人), 是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。
获取数据几种途径
爬虫的作用
需要知识
爬虫的分类
通用爬虫
聚焦爬虫
不同
Robots协议
全称是“网络爬虫排除标准”(Robots Exclusion Protocol),网站通过Robots协议告诉搜索引擎哪些页面可以抓取,哪些页面不能抓取,
例如:https://www.jd.com/robots.txt
HTTP协议
超文本传输协议
: 是用于从网络传送超文本数据到本地浏览器的传送协议HTTPS协议
HTTP+SSL
) 。 SSL(Secure Sockets Layer 安全套接层)主要用于Web的安全传输协议,在传输层对网络连接进行加密,保障在Internet上数据传输的安全。端口
SSL
请求与响应
1.域名解析 -->
2.发起TCP的3次握手 -->
3.建立TCP连接后发起http请求 -->
4.服务器响应http请求,浏览器得到html代码 -->
5.浏览器解析html代码,并请求html代码中的资源(如js、css、图片等) -->
6.浏览器对页面进行渲染呈现给用户.
URL
请求方法
常用请求头
Accept: 指定客户端能够接收的内容类型。
Accept-Charset: 浏览器可以接受的字符编码集。
Accept-Encoding: 指定浏览器可以支持的web服务器返回内容压缩编码类型。
Accept-Language: 浏览器可接受的语言。
Accept-Ranges: 可以请求网页实体的一个或者多个子范围字段。
AuthorizationHTTP: 授权的授权证书。
Cache-Control: 指定请求和响应遵循的缓存机制。
Connection: 表示是否需要持久连接。(HTTP 1.1默认进行持久连接)
CookieHTTP: 请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器。
Content-Length: 请求的内容长度。
Content-Type: 请求的与实体对应的MIME信息。
Date: 请求发送的日期和时间。
Expect: 请求的特定的服务器行为。
From: 发出请求的用户的Email。
Host: 指定请求的服务器的域名和端口号。
If-Match: 只有请求内容与实体相匹配才有效。
If-Modified-Since: 如果请求的部分在指定时间之后被修改则请求成功,未被修改则返回304代码。
If-None-Match: 如果内容未改变返回304代码,参数为服务器先前发送的Etag,与服务器回应的Etag比较判断是否改变。
If-Range: 如果实体未改变,服务器发送客户端丢失的部分,否则发送整个实体。
If-Unmodified-Since: 只在实体在指定时间之后未被修改才请求成功。
Max-Forwards: 限制信息通过代理和网关传送的时间。
Pragma: 用来包含实现特定的指令。
Proxy-Authorization: 连接到代理的授权证书。
Range: 只请求实体的一部分,指定范围。
Referer: 先前网页的地址,当前请求网页紧随其后,即来路。
TE: 客户端愿意接受的传输编码,并通知服务器接受接受尾加头信息。
Upgrade: 向服务器指定某种传输协议以便服务器进行转换(如果支持。
User-Agent 是否是浏览器。
Via: 通知中间网关或代理服务器地址,通信协议。
Warning: 关于消息实体的警告信息
响应头
Accept-Ranges: 表明服务器是否支持指定范围请求及哪种类型的分段请求。
Age: 从原始服务器到代理缓存形成的估算时间(以秒计,非负)。
Allow: 对某网络资源的有效的请求行为,不允许则返回405。
Cache-Control: 告诉所有的缓存机制是否可以缓存及哪种类型。
Content-Encodingweb: 服务器支持的返回内容压缩编码类型。。
Content-Language: 响应体的语言。
Content-Length: 响应体的长度。
Content-Location: 请求资源可替代的备用的另一地址。
Content-MD5: 返回资源的MD5校验值。
Content-Range: 在整个返回体中本部分的字节位置。
Content-Type: 返回内容的MIME类型。
Date: 原始服务器消息发出的时间。
ETag: 请求变量的实体标签的当前值。
Expires: 响应过期的日期和时间。
Last-Modified: 请求资源的最后修改时间。
Location: 用来重定向接收方到非请求URL的位置来完成请求或标识新的资源。
Pragma: 包括实现特定的指令,它可应用到响应链上的任何接收方。
Proxy-Authenticate: 它指出认证方案和可应用到代理的该URL上的参数。
refresh: 应用于重定向或一个新的资源被创造,在5秒之后重定向(由网景提出,被大部分浏览器支持)
Retry-After: 如果实体暂时不可取,通知客户端在指定时间之后再次尝试。
Serverweb: 服务器软件名称。
Set-Cookie: 设置Http Cookie。
Trailer: 指出头域在分块传输编码的尾部存在。
Transfer-Encoding: 文件传输编码。
Vary: 告诉下游代理是使用缓存响应还是从原始服务器请求。
Via: 告知代理客户端响应是通过哪里发送的。
Warning: 警告实体可能存在的问题。
WWW-Authenticate: 表明客户端请求实体应该使用的授权方案。
状态码
Decode
Encode
Get请求
word = {"wd" : "美女"} # 通过urllib.urlencode()方法,将字典键值对按URL编码转换,从而能被web服务器接受。 result = urllib.parse.urlencode(word) print(result)
result = urllib.parse.unquote(result) print(result)
response = urllib.request.urlopen(request) print(response.read())
POST请求
# POST请求的目标URL(这个代码是之前的链接,方便我们使用,不用传递sign参数,新版中该参数是加密的) url = "http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule&smartresult=ugc&sessionFrom=null" #构建表单数据 formdata = { 'i': '你好', 'from': 'AUTO', 'to': 'AUTO', 'smartresult': 'dict', 'client': 'fanyideskweb', 'doctype': 'json', 'version': '2.1', 'keyfrom': 'fanyi.web', 'action': 'FY_BY_CLICKBUTTION', 'typoResult': 'false', } formdata = urllib.parse.urlencode(formdata) formdata = formdata.encode('utf-8') req = request.Request(url, data = formdata, headers = headers) #发起请求获取响应结果 response = request.urlopen(req) #打印获取的响应结果 print (response.read().decode('utf-8'))
忽略SSL验证
from urllib import request # 1. 导入Python SSL处理模块 import ssl # 2. 表示忽略未经核实的SSL证书认证 context = ssl._create_unverified_context() # 目标url url = "https://www.12306.cn/mormhweb/" #设置请求头 headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36"} #构建request对象 request = urllib.request.Request(url, headers = headers) # 3. 在urlopen()方法里 指明添加 context 参数 response = urllib.request.urlopen(request, context = context) html = response.read().decode() print (html)
urlparse()实现URL的识别和分段
url = 'https://book.qidian.com/info/1004608738?wd=123&page=20#Catalog' """ url:待解析的url scheme='':假如解析的url没有协议,可以设置默认的协议,如果url有协议,设置此参数无效 allow_fragments=True:是否忽略锚点,默认为True表示不忽略,为False表示忽略 """ result = parse.urlparse(url=url,scheme='http',allow_fragments=True) print(result) print(result.scheme)
urlunparse()可以实现URL的构造
url_parmas = ('https', 'book.qidian.com', '/info/1004608738', '', 'wd=123&page=20', 'Catalog') #components:是一个可迭代对象,长度必须为6 result = parse.urlunparse(url_parmas) print(result) """ https://book.qidian.com/info/1004608738?wd=123&page=20#Catalog """
urljoin()传递一个基础链接,根据基础链接可以将某一个不完整的链接拼接为一个完整链接
base_url = 'https://book.qidian.com/info/1004608738?wd=123&page=20#Catalog' sub_url = '/info/100861102' full_url = parse.urljoin(base_url,sub_url) print(full_url)
parse_qs()将url编码格式的参数反序列化为字典类型
parmas_str = 'page=20&wd=123' parmas = parse.parse_qs(parmas_str) print(parmas) """ {'page': ['20'], 'wd': ['123']} """
quote()可以将中文转换为URL编码格式
word = '中国梦' url = 'http://www.baidu.com/s?wd='+parse.quote(word) print(parse.quote(word)) print(url) """ %E4%B8%AD%E5%9B%BD%E6%A2%A6 http://www.baidu.com/s?wd=%E4%B8%AD%E5%9B%BD%E6%A2%A6 """
unquote:可以将URL编码进行解码
url = 'http://www.baidu.com/s?wd=%E4%B8%AD%E5%9B%BD%E6%A2%A6' print(parse.unquote(url)) """ http://www.baidu.com/s?wd=中国梦 """
URLError
HTTPError
HTTPError是URLError的子类,我们发出一个请求时,服务器上都会对应一个response应答对象,其中它包含一个数字"响应状态码"。
专门用来处理HTTP请求错误,比如未认证,页面不存在等
有三个属性:
code:返回HTTP的状态码
reason:返回错误原因
headers:返回请求头
from urllib import request,error def check_error(): """ 因为HTTPError的父类是URLError,所以我们更好的处理顺序应该是 先捕获子类的错误,再捕获父类的错误 """ req_url = 'https://www.baiduxxx.com/' try: response = request.urlopen(url=req_url) print(response.status) except error.HTTPError as err: print(err.code,err.reason,err.headers) except error.URLError as err: print('===', err.reason)
我们之前一直都在使用的urlopen,它是一个特殊的opener,是模块帮我们创建好的。自定义Opener会有更高级的用法
import urllib.request # 构建一个HTTPHandler 处理器对象,支持处理HTTP请求 http_handler = urllib.request.HTTPHandler() # 构建一个HTTPHandler 处理器对象,支持处理HTTPS请求 # http_handler = urllib.request.HTTPSHandler() # 调用urllib.request.build_opener()方法,创建支持处理HTTP请求的opener对象 opener = urllib.request.build_opener(http_handler) # 构建 Request请求 request = urllib.request.Request("http://www.baidu.com/") # 调用自定义opener对象的open()方法,发送request请求 response = opener.open(request) # 获取服务器响应内容 print (response.read().decode())
代理设置
代理的作用:
1.突破自身IP访问限制,访问一些平时不能访问的站点。
2.访问一些单位或团体内部资源:比如使用教育网内地址段免费代理服务器,就可以用于对教育网开放的各类FTP下载上传,以及各类资料查询共享等服务。
3.提高访问速度:通常代理服务器都设置一个较大的硬盘缓冲区,当有外界的信息通过时,同时也将其保存到缓冲区中,当其他用户再访问相同的信息时,则直接由缓冲区中取出信息,传给用户,以提高访问速度。
4.隐藏真实IP:上网者也可以通过这种方法隐藏自己的IP,免受攻击。对于爬虫来说,我们用代理就是为了隐藏自身IP,防止自身的IP被封锁。
根据协议划分
FTP代理服务器**:主要用于访问FTP服务器,一般有上传、下载的功能以及缓存的功能,端口号一般为21,2121等。**
HTTP代理服务器**:主要用于访问网页,一般有内容过滤和缓存的功能,端口号一般为80、8080、3128等**
SSL/TLS代理:主要能用于访问加密的网站,一般有SSL或者TLS加密**
SOCKS代理:只是单纯的用于传输数据包,不关心具体的协议用法,速度快、有缓存功能,端口号一般为1080
根据匿名内容划分
高度匿名代理:会将数据包原封不动的转发,在服务器看来就好像真的是一个普通的用户短在访问,而记录的IP就是代理服务器的IP
普通匿名代理:会在数据包上做一些改动,服务端上有可能发现这个是代理服务器,也有一定的几率追查到客户端的真实IP.
透明代理:不但改动了数据包,还会告诉服务器客户端的真实IP,这种代理除了用缓存技术提高浏览器速度。能用内容过滤提高安全性之外,并没有其他作用。
使用代理IP这是爬虫/反爬虫的第二大招,通常也是最好用的。
代理网站
from urllib import request,error #构建支持代理的handler proxy = { 'http':'61.138.33.20:808', 'https':'120.69.82.110:44693', } proxy_handler = request.ProxyHandler( proxies=proxy ) # 构建一个私密代理Handler,需要加上私密代理账户的用户名和密码 # authproxy = { # "http" :"username:password@61.135.217.7:80" #} # authproxy_handler=urllib.request.ProxyHandler( # proxies=authproxy #) #根据proxy_handler实例化一个opener对象 opener = request.build_opener(proxy_handler) url = 'http://www.baidu.com/' # 使用https://httpbin.org/get接口验证使用了代理 # url = 'https://httpbin.org/get' try: response = opener.open(url,timeout=5) print(response.status) except error.HTTPError as err: print(err.reason) except error.URLError as err: print(err.reason) # 1. 如果按照上面代码,只有使用opener.open()方法发送 请求才使用自定义的代理,而urlopen()则不使用自定义代理。 response = opener.open(request) # 2. 将自定义的opener设置为全局的opener,之后所有的,不管是 opener.open()还是urlopen() 发送请求,都将使用自定义代理。 # request.install_opener(opener) # response = urlopen(request)
Cookies的作用
获取到一个有登录信息的Cookie模拟登陆 # -*- coding:utf-8 -*- import urllib.request url = 'https://www.douban.com/people/175417123/' #根据刚才的登录信息来构建一个已经登录过的用户的headers信息 headers = { 'User-Agent':' Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:59.0) Gecko/20100101 Firefox/59.0', 'Host':'www.renren.com', 'Cookie':'anonymid=jgoj4xlw-3izsk4; depovince=BJ; jebecookies=62d94404-de1f-450a-919b-a2d9f4c8b811|||||; _r01_=1; JSESSIONID=abchsGLNgne0L8_wz2Emw; ick_login=cf54f2dc-8b0b-417a-96b2-32d4051f7236; jebe_key=02cb19ad-2966-4641-8828-217160ca67a0%7Cba6f6d6ec917200a4e17a85dbfe33a4a%7C1525230975024%7C1%7C1525230982574; t=87a502d75601f8e8c0c6e0f79c7c07c14; societyguester=87a502d75601f8e8c0c6e0f79c7c07c14; id=965706174; xnsid=e1264d85; ver=7.0; loginfrom=null; wp_fold=0', } # 2. 通过headers里的报头信息(主要是Cookie信息),构建Request对象 request = urllib.request.Request(url, headers=headers) # 3. 直接豆瓣个人主页(主要是Cookie信息) #,判断这是一个已经登录的用户,并返回相应的页面 response = urllib.request.urlopen(request) # 4. 打印响应内容 print (response.read().decode())
CookieJar
import http.cookiejar as cookiejar from urllib import parse,request #1.构造一个CookieJar对象实例来保存cookie cookie = cookiejar.CookieJar() # 2.使用HTTPCookieProcessor()创建cookie处理器对象, # 参数为CookieJar()对象 cookie_handler = request.HTTPCookieProcessor(cookie) #3.通过build_opener()来构建opener opener = request.build_opener(cookie_handler) #4.addheaders接受一个列表,里面每一个元素都是一个headers信息的元组 #opener将会附带header信息 opener.addheaders = [ ('User-Agent','Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:59.0) Gecko/20100101 Firefox/59.0'), ] #5.需要登录账号和密码 data = { 'source': 'index_nav', 'form_email': '18518753265', 'form_password': 'ljh123456', } #6. 通过urlencode()转码 postdata = parse.urlencode(data).encode('utf-8') #7. 构建Request请求对象,包含需要发送的用户名和密码 request = request.Request("https://www.douban.com/accounts/login", data = postdata) # 8. 通过opener发送这个请求,并获取登录后的Cookie值, opener.open(request) # 9. opener包含用户登录后的Cookie值,可以直接访问那些登录后才可以访问的页面 response = opener.open("https://www.douban.com/people/175417123/") #这里为了测试不添加cookie时访问改界面的效果 #headers = { # 'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:59.0) Gecko/20100101 #Firefox/59.0', #} # request = request.Request('https://www.douban.com/people/175417123/',headers=headers) # response = request.urlopen(request) # 10. 打印响应内容 #打印结果查看是否访问成功 print(response.code) html = response.read().decode('utf-8') # print(html) with open('douban_login.html','w') as f: f.write(html)
requests
Get请求
response = requests.get("http://www.baidu.com/") * response的常用方法: * response.text 返回解码后的字符串 * respones.content 以字节形式(二进制)返回。 * response.status_code 响应状态码 * response.request.headers 请求的请求头 * response.headers 响应头 * response.encoding = 'utf-8' 可以设置编码类型 * response.encoding 获取当前的编码 * response.json() 内置的JSON解码器,以json形式返回,前提返回的内容确保是json格式的,不然解析出错会抛异常
添加请求头
import requests kw = {'wd':'美女'} headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36" } # params 接收一个字典或者字符串的查询参数, # 字典类型自动转换为url编码,不需要urlencode() response = requests.get( "http://www.baidu.com/s?", params = kw, headers = headers )
注意
Post请求
import requests req_url = "http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule&smartresult=ugc&sessionFrom=null" #分析表单数据 formdata = { 'i': '老鼠爱大米', 'from': 'AUTO', 'to': 'AUTO', 'smartresult': 'dict', 'client': 'fanyideskweb', 'doctype': 'json', 'version': '2.1', 'keyfrom': 'fanyi.web', 'action': 'FY_BY_CLICKBUTTION', 'typoResult': 'false', } #添加请求头 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( req_url, data = formdata, headers = req_header ) #print (response.text) # 如果是json文件可以直接显示 print (response.json())
上传文件
url = 'https://httpbin.org/post' files = {'file': open('image.png', 'rb')} response = requests.post(url, files=files) print(response.text)
Web客户端验证
import requests auth=('test', '123456') response = requests.get( 'http://192.168.199.107', auth = auth ) print (response.text)
代理设置
import requests # 根据协议类型,选择不同的代理 proxies = { "http": "http://11.44.156.126:4532", "https": "http://11.134.156.126:4532", } ##如果代理需要使用HTTP Basic Auth,可以使用下面这种格式: ''' proxy = { "http": "name:password@11.134.156.126:4532" } ''' response = requests.get( "http://www.baidu.com", proxies = proxies ) print(response.text)
Cookies
import requests response = requests.get("https://www.douban.com/") # 7\. 返回CookieJar对象: cookiejar = response.cookies # 8\. 将CookieJar转为字典: cookiedict = requests.utils.dict_from_cookiejar( cookiejar ) print (cookiejar) print (cookiedict)
Session
import requests # 1\. 创建session对象,可以保存Cookie值 ssion = requests.session() # 2\. 处理 headers headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36" } # 3\. 需要登录的用户名和密码 data = { "email":"18518753265", "password":"ljh123456" } # 4\. 发送附带用户名和密码的请求,并获取登录后的Cookie值,保存在ssion里 ssion.post( "http://www.renren.com/PLogin.do", data = data ) # 5\. ssion包含用户登录后的Cookie值,可以直接访问那些登录后才可以访问的页面 response = ssion.get( "http://www.renren.com/965722397/profile" ) # 6\. 打印响应内容 print (response.text)
跳过SSL验证
import requests response = requests.get("https://www.12306.cn/mormhweb/", verify = False) print (response.text)
正则
在线网站
为什么要学正则
compile 函数
match 方法:
从起始位置开始查找,一次匹配
import re pattern = re.compile('\d', re.S) result = re.match(pattern, '12') print(result.group())
search 方法:
从任何位置开始查找,一次匹配
import re pattern = re.compile('\d', re.S) result = re.search(pattern, 'a12') print(result.group())
findall 方法:
全部匹配,返回列表
import re pattern = re.compile('\d', re.S) result = re.findall(pattern, 'a12') print(result)
split 方法:
分割字符串,返回列表
import re pattern = re.compile('\d', re.S) result = re.split(pattern, 'a1b2c') print(result)
sub 方法:
替换
import re pattern = re.compile('\d', re.S) result = re.sub(pattern, 'a', '1234') print(result)
数据格式 | 描述 | 作用 |
---|---|---|
XML | 可扩展标记语言 | 用来传输和存储数据 |
HTML | 超文本标记语言 | 用来显示数据 |
表达式 | 含义 |
---|---|
/ | 从根节点开始 |
// | 从任意节点 |
. | 从当前节点 |
… | 从当前节点的父节点 |
@ | 选取属性 |
text() | 选取文本 |
from lxml import etree data = """ <div> <ul> <li class="item-0"><a href="link1.html">first item</a></li> <li class="item-1"><a href="link2.html">second item</a></li> <li class="item-inactive"><a href="link3.html">third item</a></li> <li class="item-1" id="1" ><a href="link4.html">fourth item</a></li> <li class="item-0" data="2"><a href="link5.html">fifth item</a> </ul> </div> """ html = etree.HTML(data)#构造了一个XPath解析对象。etree.HTML模块可以自动修正HTML文本。 li_list = html.xpath('//ul/li')#选取ul下面的所有li节点 #li_list = html.xpath('//div/ul/li')#选取ul下面的所有li节点 a_list = html.xpath('//ul/li/a')#选取ul下面的所有a节点 herf_list = html.xpath('//ul/li/a/@href')#选取ul下面的所有a节点的属性herf的值 text_list = html.xpath('//ul/li/a/text()')#选取ul下面的所有a节点的值 print(li_list) print(a_list) print(herf_list) print(text_list) #打印 [<Element li at 0x1015f4c48>, <Element li at 0x1015f4c08>, <Element li at 0x1015f4d08>, <Element li at 0x1015f4d48>, <Element li at 0x1015f4d88>] [<Element a at 0x1015f4dc8>, <Element a at 0x1015f4e08>, <Element a at 0x1015f4e48>, <Element a at 0x1015f4e88>, <Element a at 0x1015f4ec8>] ['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html'] ['first item', 'second item', 'third item', 'fourth item', 'fifth item']
通配符 | 含义 |
---|---|
* | 选取任何元素节点 |
@* | 选取任何属性的节点 |
from lxml import etree data = """ <div> <ul> <li class="item-0"><a href="link1.html">first item</a></li> <li class="item-1"><a href="link2.html">second item</a></li> <li class="item-inactive"><a href="link3.html">third item</a></li> <li class="item-1" id="1" ><a href="link4.html">fourth item</a></li> <li class="item-0" data="2"><a href="link5.html">fifth item</a> </ul> </div> """ html = etree.HTML(data) li_list = html.xpath('//li[@class="item-0"]')#选取class为item-0的li标签 text_list = html.xpath('//li[@class="item-0"]/a/text()')#选取class为item-0的li标签 下面a标签的值 li1_list = html.xpath('//li[@id="1"]')#选取id属性为1的li标签 li2_list = html.xpath('//li[@data="2"]')#选取data属性为2的li标签 print(li_list) print(text_list) print(li1_list) print(li2_list) #打印 [<Element li at 0x101dd4cc8>, <Element li at 0x101dd4c88>] ['first item', 'fifth item'] [<Element li at 0x101dd4d88>] [<Element li at 0x101dd4c88>]
表达式 | 含义 |
---|---|
[?] | 选取第几个节点 |
last() | 选取最后一个节点 |
last()-1 | 选取倒数第二个节点 |
position()-1 | 选取前两个 |
from lxml import etree data = jiayuan html = etree.HTML(data) li_list = html.xpath('//ul/li[1]') # 选取ul下面的第一个li节点 li1_list = html.xpath('//ul/li[last()]') # 选取ul下面的最后一个li节点 li2_list = html.xpath('//ul/li[last()-1]') # 选取ul下面的最后一个li节点 li3_list = html.xpath('//ul/li[position()<= 3]') # 选取ul下面前3个标签 text_list = html.xpath('//ul/li[position()<= 3]/a/@href') # 选取ul下面前3个标签的里面的a标签里面的href的值 print(li_list) print(li1_list) print(li2_list) print(li3_list) print(text_list) #打印 [<Element li at 0x1015d3cc8>] [<Element li at 0x1015d3c88>] [<Element li at 0x1015d3d88>] [<Element li at 0x1015d3cc8>, <Element li at 0x1015d3dc8>, <Element li at 0x1015d3e08>] ['link1.html', 'link2.html', 'link3.html']
函数名 | 含义 |
---|---|
starts-with | 选取以什么开头的元素 |
contains | 选取包含一些信息的元素 |
and | 并且的关系 |
or | 或者的关系 |
from lxml import etree data = """ <div> <ul> <li class="item-0"><a href="link1.html">first item</a></li> <li class="item-1"><a href="link2.html">second item</a></li> <li class="item-inactive"><a href="link3.html">third item</a></li> <li class="item-1" id="1" ><a href="link4.html">fourth item</a></li> <li class="item-0" data="2"><a href="link5.html">fifth item</a> </ul> </div> """ html = etree.HTML(data) li_list = html.xpath('//li[starts-with(@class,"item-1")]')#获取class包含以item-1开头的li标签 li1_list = html.xpath('//li[contains(@class,"item-1")]')#获取class包含item的li标签 li2_list = html.xpath('//li[contains(@class,"item-0") and contains(@data,"2")]')#获取class为item-0并且data为2的li标签 li3_list = html.xpath('//li[contains(@class,"item-1") or contains(@data,"2")]')#获取class为item-1或者data为2的li标签 print(li_list) print(li1_list) print(li2_list) print(li3_list) #打印 [<Element li at 0x101dcac08>, <Element li at 0x101dcabc8>] [<Element li at 0x101dcac08>, <Element li at 0x101dcabc8>] [<Element li at 0x101dcacc8>] [<Element li at 0x101dcac08>, <Element li at 0x101dcabc8>, <Element li at 0x101dcacc8>]
import requests from lxml import etree import os ''' # 爬取的网站:url https://www.pkdoutu.com/article/list/?page=2 # 解析到图片的思路 //div[@class="col-sm-9 center-wrap"]//a //div[@class="col-sm-9 center-wrap"]//a/div[@class="random_title"]/text() //div[@class="col-sm-9 center-wrap"]//a/div[@class="random_article"]//img/@data-original ''' class DouTuLaSpider(): def __init__(self): self.url = 'https://www.pkdoutu.com/article/list/?page=' self.headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36' } # 发送请求 def send_request(self, url): print(url) response = requests.get(url=url, headers=self.headers) return response def parse_content(self, response): html = response.text content = etree.HTML(html) a_list = content.xpath('//div[@class="col-sm-9 center-wrap"]//a') print(a_list) for a in a_list: title = a.xpath('./div[@class="random_title"]/text()') # xpath取出来的是列表 pic_list = a.xpath('./div[@class="random_article"]//img/@data-original') if title: if not os.path.exists('doutu/' + title[0]): os.mkdir('doutu/' + title[0]) for index, pic in enumerate(pic_list): response = self.send_request(pic) # 发送图片请求 name = str(index + 1) + "_" + pic[-20:] # 图片名字 self.write_content(response, name, 'doutu/' + title[0]) def write_content(self, response, name, path): print('正在写入%s' % name) with open(path + '/' + name, 'wb') as f: f.write(response.content) def start(self): for i in range(10, 20): full_url = self.url + str(i) reponse = self.send_request(full_url) self.parse_content(reponse) if __name__ == '__main__': dtl = DouTuLaSpider() dtl.start()
pip install beautifulsoup4
表达式 | 使用方法 | 优势 |
---|---|---|
Python标准库 | BeautifulSoup(markup, “html.parser”) | Python的内置标准库 执行速度适中 文档容错能力强 |
lxml HTML 解析器 | BeautifulSoup(markup, “lxml”) | 速度快 文档容错能力强 |
lxml XML 解析器 | BeautifulSoup(markup, [“lxml-xml”]) BeautifulSoup(markup, “xml”) | 速度快 唯一支持XML的解析器 |
html5lib | BeautifulSoup(markup, “html5lib”) | 最好的容错性 以浏览器的方式解析文档 生成HTML5格式的文档 |
对象种类
Tag
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>') tag = soup.b type(tag) # <class 'bs4.element.Tag'>
Name
tag.name # 'b'
attrs
tag.attrs # {u'class': u'boldest'}
NavigableString
tag.string #Extremely bold
搜索文档树
html_doc = """ <html><head><title>The Dormouse's story</title></head> <body> <p class="title"><b>The Dormouse's story</b></p> <p class="story">Once upon a time there were three little sisters; and their names were <a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>, <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>; and they lived at the bottom of a well.</p> <p class="story">...</p> """ from bs4 import BeautifulSoup soup = BeautifulSoup(html_doc, 'html.parser')
find_all(name, attrs, recursive, text, **kwargs)
字符串
soup.find_all('b') # [<b>The Dormouse's story</b>]
正则
import re for tag in soup.find_all(re.compile("^b")): print(tag.name) # body # b
列表
soup.find_all(["a", "b"]) # [<b>The Dormouse's story</b>, # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
关键字
soup.find_all(id='link2') # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>] soup.find_all(href=re.compile("elsie")) # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
按CSS搜索
soup.find_all("a", class_="sister") # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
CSS选择器
soup.select("title") # [<title>The Dormouse's story</title>] soup.select("p nth-of-type(3)") # [<p class="story">...</p>]
通过tag标签逐层查找
soup.select("body a") # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>] soup.select("html head title") # [<title>The Dormouse's story</title>]
找到某个tag标签下的直接子标签
soup.select("head > title") # [<title>The Dormouse's story</title>] soup.select("p > a") # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>] soup.select("p > a:nth-of-type(2)") # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>] soup.select("p > #link1") # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>] soup.select("body > a") # []
找到兄弟节点标签:
soup.select("#link1 ~ .sister") # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>] soup.select("#link1 + .sister") # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
通过CSS的类名查找
soup.select(".sister") # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>] soup.select("[class~=sister]") # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
通过tag的id查找:
soup.select("#link1") # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>] soup.select("a#link2") # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
同时用多种CSS选择器查询元素:
soup.select("#link1,#link2") # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
通过是否存在某个属性来查找:
soup.select('a[href]') # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
通过属性的值来查找:
soup.select('a[href="http://example.com/elsie"]') # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>] soup.select('a[href^="http://example.com/"]') # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>] soup.select('a[href$="tillie"]') # [<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>] soup.select('a[href*=".com/el"]') # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
返回查找到的元素的第一个
soup.select_one(".sister") # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
使用流程:
- 导包:from bs4 import BeautifulSoup
- 使用方式:可以将一个html文档,转化为BeautifulSoup对象,然后通过对象的方法或者属性去查找指定的节点内容
(1)转化本地文件:
- soup = BeautifulSoup(open(‘本地文件’), ‘lxml’)
(2)转化网络文件:
- soup = BeautifulSoup(‘字符串类型或者字节类型’, ‘lxml’)
(3)打印soup对象显示内容为html文件中的内容
基础巩固:
(1)根据标签名查找
- soup.a 只能找到第一个符合要求的标签
(2)获取属性
- soup.a.attrs 获取a所有的属性和属性值,返回一个字典
- soup.a.attrs[‘href’] 获取href属性
- soup.a[‘href’] 也可简写为这种形式
(3)获取内容
- soup.a.string
- soup.a.text
- soup.a.get_text()
【注意】如果标签还有标签,那么string获取到的结果为None,而其它两个,可以获取文本内容
(4)find:找到第一个符合要求的标签
- soup.find(‘a’) 找到第一个符合要求的
- soup.find(‘a’, title=“xxx”)
- soup.find(‘a’, alt=“xxx”)
- soup.find(‘a’, class_=“xxx”)
- soup.find(‘a’, id=“xxx”)
(5)find_all:找到所有符合要求的标签
- soup.find_all(‘a’)
- soup.find_all([‘a’,‘b’]) 找到所有的a和b标签
- soup.find_all(‘a’, limit=2) 限制前两个
(6)根据选择器选择指定的内容
select:soup.select(’#feng’)
- 常见的选择器:标签选择器(a)、类选择器(.)、id选择器(#)、层级选择器
- 层级选择器:
div .dudu #lala .meme .xixi 下面好多级
div > p > a > .lala 只能是下面一级
【注意】select选择器返回永远是列表,需要通过下标提取指定的对象
jsonpath
用来解析多层嵌套的json数据;JsonPath 是一种信息抽取类库,是从JSON文档中抽取指定信息的工具,提供多种语言实现版本,包括:Javascript, Python, PHP 和 Java
文档与安装
用法
import requests import jsonpath import json import chardet url = 'http://www.lagou.com/lbs/getAllCitySearchLabels.json' response = requests.get(url) html = response.text # 把json格式字符串转换成python对象 jsonobj = json.loads(html) # 从根节点开始,匹配name节点 citylist = jsonpath.jsonpath(jsonobj,'$..name')
多线程回顾
队列
栈
栈(stacks)是一种只能通过访问其一端来实现数据存储与检索的线性数据结构,具有后进先出(last in first out,LIFO)的特征
线程池爬虫
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import as_completed import requests url = 'https://www.baidu.com/s?wd=%E7%BE%8E%E5%A5%B3&pn=' # 发起请求 def request(url): # 可以用不定长参数 print(url) response = requests.get(url) return response def parse(result): ''' 解析 :param result: :return: ''' return ['https://www.baidu.com/'] # 返回新的url def main(): with ThreadPoolExecutor(max_workers=28) as executor: url_list = [] # 装URL的列表 for i in range(1, 11): # 一共发起10也 full_url = url + str((i - 1) * 10) url_list.append(full_url) result = executor.map(request, url_list) for res in result: new_url = parse(res) # 去解析 result1 = executor.map(request, new_url) # 继续请求 for res1 in result1: print(res1) # 第二种 ''' with ThreadPoolExecutor(max_workers=28) as executor: future_list = [] for i in range(1, 11): # 一共发起10也 full_url = url + str((i - 1) * 10) future= executor.submit(request, full_url) future_list.append(future) for res in as_completed(futrue_list): print(res.result()) ''' if __name__ == '__main__': main()
进程池爬虫
from concurrent.futures import ProcessPoolExecutor from concurrent.futures import as_completed import requests url = 'https://www.baidu.com/s?wd=%E7%BE%8E%E5%A5%B3&pn=' # 发起请求 def request(url): # 可以用不定长参数 print(url) response = requests.get(url) return response def parse(result): ''' 解析 :param result: :return: ''' return ['https://www.baidu.com/'] # 返回新的url def main(): with ProcessPoolExecutor(max_workers=3) as executor: url_list = [] # 装URL的列表 for i in range(1, 11): # 一共发起10也 full_url = url + str((i - 1) * 10) url_list.append(full_url) result = executor.map(request, url_list) for res in result: print(res) new_url = parse(res) # 去解析 result1 = executor.map(request, new_url) # 继续请求 for res1 in result1: print(res1) '''第二种 with ProcessPoolExecutor(max_workers=3) as executor: future_list = [] for i in range(1, 11): # 一共发起10也 full_url = url + str((i - 1) * 10) future = executor.submit(request, full_url) future_list.append(future) for res in as_completed(future_list): print(res.result()) ''' if __name__ == '__main__': main()
多协程爬虫
import requests import gevent from gevent import monkey from gevent.pool import Pool #把当前的IO操作,打上标记,以便于gevent能检测出来实现异步(否则还是串行) monkey.patch_all() def task(url): ''' 1、request发起请求 :param url: :return: ''' response = requests.get(url) print(response.status_code) #控制最多一次向远程提交多少个请求,None代表不限制 pool = Pool(5) gevent.joinall([ pool.spawn(task,url='https://www.baidu.com'), pool.spawn(task,url='http://www.sina.com.cn'), pool.spawn(task,url='https://news.baidu.com'), ]) gevent+reqeust+Pool(控制每次请求数量)
Selenium是一款自动化测试工具,支持Chrome,Safari,Firefox 等主流界面式浏览器;支持多种语言开发,比如Java,C,Python等
pip install selenium
http://npm.taobao.org/mirrors/chromedriver
#导入 webdriver from selenium import webdriver # 要想调用键盘按键操作需要引入keys包 from selenium.webdriver.common.keys import Keys import time #无界面浏览器相关设置 # 创建chrome参数对象 opt = webdriver.ChromeOptions() #把chrome设置成为无界面模式 opt.set_headless() #创建chrome无界面对象 driver = webdriver.Chrome( options=opt, executable_path='/Users/ljh/Desktop/chromedriver' ) #创建chrome有界面对象 #调用Chrome浏览器创建浏览器对像(指定一下位置) driver = webdriver.Chrome( executable_path='/Users/ljh/Desktop/chromedriver' ) #打开浏览器,模拟浏览器请求页面 driver.get('http://www.baidu.com/') #获取页面的信息 html = driver.page_source print(html) # 获取页面名为 wrapper的id标签的文本内容 data = driver.find_element_by_id("wrapper").text #获取标签的属性 attrvaule = driver.find_element_by_id("wrapper").get_attribute('class') #打印数据内容 print(data) #打印标题数据 print(driver.title) #向百度的搜索框输入搜索关键字 driver.find_element_by_id('kw').send_keys('美女') #百度搜索按钮,click() 是模拟点击 driver.find_element_by_id('su').click() #获取当前页面的cookies() cookies = driver.get_cookies() cookie = '' for item in cookies: cookie += item['name']+item['value']+' ;' print(cookie[:-1]) #全选输入框中的内容ctrl+a print(driver.find_element_by_id('kw').send_keys(Keys.CONTROL, 'a')) # ctrl+x 剪切输入框内容 driver.find_element_by_id("kw").send_keys(Keys.CONTROL, 'x') #清空输入框内容 driver.find_element_by_id('kw').clear() #输入框重新输入内容 driver.find_element_by_id('kw').send_keys('风景') #模拟回车键 driver.find_element_by_id('su').send_keys(Keys.RETURN) #获取当前的url currentUrl = driver.current_url print(currentUrl) #截取网页页面(生成当前的页面快照并保存) driver.save_screenshot('baidu.png') #睡眠7秒 time.sleep(7) # 关闭浏览器 driver.quit() # 关闭当前页面,如果只有一个页面,会关闭浏览器 driver.close()
opt = webdriver.ChromeOptions() opt.add_argument("--proxy-server=http://118.20.16.82:9999")
self.browser.add_cookie({ 'domain': '.xxxx.com', 'name': cookie['name'], 'value': cookie['value'], 'path': '/',#哪个页面添加Cookie 'expires': None })
显式等待是你在代码中定义等待一定条件发生后再进一步执行你的代码。 最糟糕的案例是使用time.sleep(),它将条件设置为等待一个确切的时间段。 这里有一些方便的方法让你只等待需要的时间。WebDriverWait结合ExpectedCondition 是实现的一种方式。
from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC driver = webdriver.Firefox() driver.get("http://somedomain/url_that_delays_loading") try: element = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, "myDynamicElement")) ) finally: driver.quit()
如果某些元素不是立即可用的,隐式等待是告诉WebDriver去等待一定的时间后去查找元素。 默认等待时间是0秒,一旦设置该值,隐式等待是设置该WebDriver的实例的生命周期。
from selenium import webdriver driver = webdriver.Firefox() driver.implicitly_wait(10) # seconds driver.get("http://somedomain/url_that_delays_loading") myDynamicElement = driver.find_element_by_id("myDynamicElement")
driver.execute_script('window.scrollTo(0,document.body.scrollHeight)')
options = webdriver.ChromeOptions() # 添加无界面参数 options.add_argument('--headless') browser = webdriver.Chrome(options=options)
# 获取当前所有句柄(窗口) all_handles = browser.window_handles # 切换browser到新的窗口,获取新窗口的对象 browser.switch_to.window(all_handles[1])
pip3 install Scrapy