网络参数
1.header下
(1)user-agent
:这个是爬虫header里可以说必须的东西,基本网站肯定第一个都是判断这个
(2)referer
:代表你是从哪来访问到这个界面的,也是比较经常拿来判断是否为爬虫的参数
(3)accept-encoding
:代表用什么进行数据压缩,有时候如果压缩了数据会变化,甚至乱码,这时候可能header里面删掉这个比较好
(4)cookie
:这个也是非常重要的内容,但经常都是有时间限制
2.data
下
(1)salt
:js加密
一般都是在服务器对数据加密后传输,而加密的内容可能原来就几位数,如果准备了一个字典进行暴力破解有时候也不是难事,为了应对这种情况,有时候就会用salt,也就是一大串很长的字符串,和加密结合,从而做到一定程度的反爬虫
(2)sign
:签名,也是一种安全认证机制,和salt一样一般加密方法都写在js里,此时去js找到后翻译成python代码模拟生成其内容,js代码不懂什么意思可以在浏览器中按f12
进入console
中运行
接入网络
导入urllib
包,里面包括4个模块——request
、error
、parse
、robotparser
,连接要用的就是request
模块
urllib下模块详解
1.request
负责一些网络连接的请求
(1)urlopen(url, data=none, [timeout,])
用此函数连接,第一个参数是网址
,第二个参数默认none
,说明GET
方法,如果赋值了就是以input
的方式提交,举例:
import urllib.request
response = urllib.request.urlopen("http://www.whatismyip.com.tw")
#连接网页,并保存到response,该网页代码少,不会卡...
html = response.read() #读取页面内容
html = html.decode("utf-8") #将编码改成utf-8
print(html) #输出内容,即网页源代码
(2)geturl()
返回你连接的url
(3)info()
返回远程服务器的header
信息
(4)getcode()
返回http状态
信息,比如200
说明没问题,404
是not found等等
(5)urlretrieve(url, filename=None, reporthook=None, data=None)
直接将远程数据下载到本地,url
表示你要下载的东西地址(这个地址打开得是一个文件,比如图片、压缩包之类的,如果只是个页面就下载不了),finename
表示你要给下载的文件取啥名字,reporthook
是一个回调函数,可以显示当前的下载进度,data
是post
到服务器的数据,该方法返回一个包含两个元素的(filename,headers)
元组,filename
表示保存到本地的路径,header
表示服务器的响应头,举例:
for each in jpg:
filename = each.split('/')[-1]
#这个each是一个类似:https://imgsa.baidu.com/forum/w%3D580/sign=xxx.jpg的图片网址
#filename就是从最后一个'/'开始到后面的文件名
urllib.request.urlretrieve(each, filename, None) #下载该图片并取名
2.parse
对数据的处理
(1)urlencode()
对提交的数据进行转码,举例:
data = urllib.parse.urlencode(data).encode("utf-8") #encode就是unicode文件转成utf-8编码形式
response = urllib.request.urlopen(url, data) #把utf-8解码成unicode形式
注:
encode
函数是将一个unicode
类按照指定的编码(如果不指定则使用defaultencoding
)转换为不带编码标记的str
类,即byte
字节流;decode
函数是将一个str
类按照指定编码(如果不指定则使用defaultencoding
)转换为使用utf-8
编码的unicode
类
3.chardet
自动检测编码,举例:
import chardet
response = urllib.request.urlopen(url)
html = response.read()
cs = chardet.detect(html)
print(cs) #输出一个字典,里面记录了该网页的编码、语言等(但也可能会有错)
html = html.decode(cs.get("encoding", 'utf-8"))
#将页面按chardet解析的编码解码,如果不行就用第二个参数utf-8解码
print(html) #解码后的页面
实战
1.隐藏
网页有时会根据User-Agent
来判断是否为正常用户访问,从而将非用户访问屏蔽,所以使用爬虫时要仿照浏览器信息来隐藏自己的爬虫身份,具体做法:
(1)Request
对象生成前新建一个head
参数(需为字典),里面放入User-Agent
信息传入(referer
有时也挺需要的,当然参数能尽量都传会更真实点),并且request要采用request.Request(url, data, head)
方法,举例:
head = {}
head['User-Agent'] = '浏览器身份'
req = urllib.request.Request(url, data, head) #连接前添加head
response = urllib.request.urlopen(req)
(2)request对象生成后通过Request.add_header('key','val')
方法修改,举例:
req = urllib.request.Request(url, data) #连接前不用head
req.add_header('User-Agent','浏览器身份') #添加head
response = urllib.request.urlopen(req)
2.爬取真人化
当使用爬虫时无时无刻都是爬取数据,这种非常人的行为会引起怀疑,所以需要使用设置阈值来进行周期控制或者设置代理
(1)设置阈值
引入time
模块,使用sleep()
方法控制单次时间,举例:
time.sleep(2) #延迟2秒后运行
(2)代理
利用多个代理同时爬取(比如去:http://cn-proxy.com/
找,需要翻墙),步骤:第一步设置一个字典包含代理服务器,格式:{'协议':'ip:port'}
,然后将字典放入ProxyHandler()
方法内;第二步用build_opener()
方法定制、创建一个opener
,并将代理放入;第三步用install_opener()
方法安装opener
,当然前面这个方法是全程只使用这个opener,如果想只在需要时使用可以用opener.open()
方法,并把代理链接放入,举例:
proxy_support=urllib.request.ProxyHandler({'http':'39.134.10.11:8080'})
opener = urllib.request.build_opener(proxy_support)
#print(proxy_support.proxies) #想看自己用的代理ip时用这个可以看
opener.addheaders = [('User-Agent','Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36')]
#加入header,可选
urllib.request.install_opener(opener)
注:
可以同时设置多个代理,设置一个列表,然后将列表放入ProxyHandler()
方法里,举例:
iplist=['221.130.253.135:8090','39.134.10.11:8080','112.21.164.58:1080'] #代理ip列表
proxy_support=urllib.request.ProxyHandler({'http':random.choice(iplist)}) #随机选取一个ip
3.不在html上的内容
当要爬取的数据不在源代码上,说明可能是以json
数据传输,所以如果要截获它,首先在浏览器下按f12
,选择network
下,刷新后会发现是很多文件加载组成的这个网页,我们需要的数据都在这里面,比如网易云评论。那么多文件一个个找显然不合理,所以可以通过调慢网速然后等一个个文件加载,当有我们想要的时候就暂停,然后找到这个文件,后面就好做了,这里步骤就是:
(1)勾上Disable cache
,网速调慢比如regular 2G
(在Disable cache右边应该),然后左上边有个红色的按钮记住
(2)刷新网页,当我们想要的东西出现时停止刷新(把刷新的×
点了),然后也点前面那个红色的按钮,剩下的东西也就会停止加载了,这个时候基本要的东西也出来了,就可以找了
(3)在XHR
和Doc
文件类型中找找,一般数据都传这里面(看preview
,response
也就是整理好的样式),找到以后就可以看它请求的url了,然后的一些data
数据啥的拷下来,接下来看他的post
还是get
方法,然后传相应数据就可以了
(4)如果数据保存在json当中,可以通过导入json模块,并使用json.loads(res.text)
方法来读取,此时内容会被保存为字典形式,提取时就可以按dict[]
方式了
异常处理
需要先导入urllib.error
Http返回状态码
100——299 代表成功
400——499 代表客户端问题导致的连接失败
500——599 代表服务端出问题导致连接失败
更多可以导入requests
,然后输入requests.codes
,然后看源代码。
异常写法一
try:
response = urllib.request.urlopen(req)
except urllib.error.HTTPError as e:
print(e.code) #HTTP输出code
except urllib.error.URLError as e: #记住这种写法要把HTTP的写在URL的前面
print(e.reason) #URL输出reason
else:
#normal situlation
异常写法二(推荐)
try:
response = urllib.request.urlopen(req)
except urllib.error.URLError as e:
if hasattr(e, 'reason'):
print(e.reason)
elif hasattr(e, 'code'):
print(e.code)
else:
#normal situlation
更好的第三方库
python里有更好的模块用于爬虫,忘了上面自带的,通过:pip install
安装requests
模块和bs4
模块,这两个模块的使用很简单,最主要有两个方法:requests.get()
和bs4.BeautifulSoup()
,第一个负责连接网页并获取内容,第二个方法能把内容转码并保存在容器中
1.requests模块
(1)get(url[,param=payload,headers=headers...])
get
方法连接网页,可选是否传参和引入报头,如果要传参或引入报头,传入内容需为字典形式,当证书有问题时,比如12306不是使用ssl
协议,所以用https登录会提示不可信的链接,即报错,这时候在get参数里加上一个:verify=False
就可以了,举例:
requests.get(url, headers=header, verify=False)
不过这时候虽然能连接,但可能还是会有警告,所以在连接前下面这句捕捉警告就可以了:
logging.captureWarnings(True)
注:
get方法里可以传参数方法:
①requests.get('url?key1=value1&key2=value2&...')
②requests.get(url, params={'key1':'value1', 'key2':'value2', ...})
(2)post(url[,data=data,headers=headers...])
post
方法连接网页,使用方法参考get
注:
requests连接返回的是一个response
类型,直接打印是连接状态码(成功为200
),其内容(即response.text
)为str
类型,上面两个连接方法可以传入的参数很多,不只是参数、报头,还有:
代理 proxies=proxies,其中proxies格式:{"http":"ip:port"}
cookies cookies=cookies,可以登录后把cookie信息放到header里然后传进去
请求时间 timeout=几秒
json数据 json=payload
文件 files=files
data
(3)encoding
查看获取文本的编码,举例:
res = requests.get(url)
res.encoding
#结果输出编码格式,如果要修改输出编码格式就给这个赋值,例如:res.encoding = 'gb2312'
(4)text
查看获取的页面内容,一般是源代码之类的文本信息
(5)content
也是获取的页面内容,但这个返回的是字节流,如果是要下载图片、视频或者网页(整个网页而不只是源代码)之类的就用这个,比如下载一张网上的图片:
with open('a.jpg','wb') as f:
f.write(res.content)
(6)status_code
返回访问页面的状态码(一般200是访问成功的状态码),我们可以用这个来判断页面是否访问成功(raise_for_status()
抛出失败请求异常),举例:
if res.status_code == 200:
print("success")
(7)headers
以字典形式返回报头,里面有:Content-Type
、server
、connection
等很多内容,比如我只想知道是啥内容类型可以:res.headers['Content-Type']
或者res.headers.get('Content-Type')
(8)cookies
返回cookies
(9)history
可以返回是否内容被重定向,比如原来是http
变成https
(10)url
可以返回你访问的网页的网址,比如你访问"http://www.baidu.com",但是肯定会变成"https://www.baidu.com",所以这时候res.url
就会返回后一个网址
注:
(3)——(10)都是在连接后的对象里进行的操作
(11)Session()
会话维持,可以模拟登陆,然后就共享这一个会话,像如果两次访问一个界面,会话是不同的,所以cookies
也是不一样,比如:
data = {'name':'aaa','password':'111'}
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36'}
r = requests.post("http://localhost:8080/BookShop/Servlet",data=data, headers=headers) #访问1
s = requests.get('http://localhost:8080/BookShop/Servlet') #访问2,get没有cookie
print(r.cookies) #因为2次访问会话不同,可以发现cookies也不一样
print(s.cookies)
结果:
<RequestsCookieJar[<Cookie JSESSIONID=2E70361488F66F167EB148A6823C396C for localhost.local/BookShop>]>
<RequestsCookieJar[]>
而用Session()以后,这几个访问都在同一个会话当中,cookies也是相同的,举例:
import requests
s = requests.Session() #创建一个会话
data = {'name':'aaa','password':'111'}
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36'}
s.get('http://localhost:8080/BookShop/Servlet') #先访问这个页面获得会话,但并没有cookie
r = s.post("http://localhost:8080/BookShop/Servlet",data=data, headers=headers) #这次访问还是跟上次一样的会话
print(r.cookies) #会发现两个都有cookies,且值相同
print(s.cookies)
结果为:
<RequestsCookieJar[<Cookie JSESSIONID=101406D2016C041539F2BD40CE47696E for localhost.local/BookShop>]>
<RequestsCookieJar[<Cookie JSESSIONID=101406D2016C041539F2BD40CE47696E for localhost.local/BookShop>]>
(12)json()
如果响应的结果是个json
格式的内容,可以直接用requests.json()
保存下来,比如:
res = requests.get('http://www.httpbin.org/get') #该页面访问结果是一个json数据
print(res.text)
print(res.json()) #以json格式保存
(13)codes
输入requests.codes
,查看源代码可以看到所有状态码对应的意义,并且从其集合里也可以找到对应的代替这个状态码的字符,比如200
的集合里有ok
,所以可以用requests.codes.ok
代替200,更多可以看源代码里写的(比如requests.codes.not_found
==404),这里挖一部分源代码:
# Informational.
100: ('continue',)
200: ('ok', 'okay', 'all_ok', 'all_okay', 'all_good', '\\o/', '✓')
# Redirection.
302: ('found',)
305: ('use_proxy',)
306: ('switch_proxy',)
# Client Error.
400: ('bad_request', 'bad')
403: ('forbidden',)
404: ('not_found', '-o-')
# Server Error.
500: ('internal_server_error', 'server_error', '/o\\', '✗')
502: ('bad_gateway',)
504: ('gateway_timeout',)
异常
requests下异常导入:from requests.exceptions import RequestException
,然后就可以直接用了:
try:
res = request.get(url)
…
except RequestException:
…
更多参考
https://blog.csdn.net/imail2016/article/details/72381989
官方中文文档
http://cn.python-requests.org/zh_CN/latest/
2. BeautifulSoup4模块
(导入时import bs4
)
(1)BeautifulSoup(获取的内容,解析器)
比如用res = requests.get()
获取页面后可以通过这个方法获取内容,如果要源代码文本,就第一个参数是:res.text
,要流文件就:res.content
,参考上面requests模块可以获取的内容,第二个解析器默认的话用html.parser
,用lxml
也可以,如果是xml
语言,则解析器用xml,但后面两个解析器需要安装C语言库,使用举例:
soup = bs4.BeautifulSoup(res.text, 'html.parser')
(2)soup.target
可以直接通过:soup.标签
来获取内容,比如:print(soup.title)
就可以知道title
标签里的内容了(不过标签也会附上,如果只要内容,就在后面再加.string
),但比如有很多个p
标签,那么:soup.p
就只返回第一个p标签的内容,我们还可以获取标签的属性值,比如:print(soup.img['name'])
,但要注意的是因为只能获取第一个标签的信息,所以第一个标签如果没有这个属性就会报错,比如上面这个img
的,如果第二个img有name
属性,第一个没有,那么就会报错,为了获得的第一个标签是我们想要的,我们可以嵌套着获取标签,比如:
print(soup.head.title.string)
还有想要知道子标签可以用:content
、children
,比如要知道p标签下所有标签:soup.p.content
;要知道父标签就parent
,如果要知道所有祖先标签,即父标签、父父标签、所有他上级的标签的话用parents
;要知道兄弟标签则next_siblings
(3)soup.prettify()
格式化代码,输出时会好看些
(4)find_all(['标签名',属性='值'])
可以从这源代码当中获取标签,比如获取所有的<a href=''></a>
即<a>标签可以:
targets = soup.find_all('a')
如果还想获得的a标签的class
属性为abc,则:
targets = soup.find_all('a',class_='abc')
class在python中作为类的关键字,所以要查找网页中的class标签时应该在后面加下划线,即class_
,也可以不根据标签而是满足某个属性:
targets = soup.find_all(class_="BDE_Smiley")
并且该方法还可以以集合或者字典形式传入多个参数,比如获取多个标签:
targets = soup.find_all({"img", "title", "head"})
获取多种条件的一个标签:
targets = soup.find_all("img", {"class":{"BDE_Image","BDE_Smiley"}})
还有就是必须用集合形式来传参的情况,比如有些有标签属性带-
,而此时就会报错(只允许有_
和字母数字),举例:
ip_s = soup.find_all('td', attrs={'data-title':'IP'})
#data-title带-,不能写成data-title='IP'
注:
find_all
返回的是个结果集,所以targets
类型是结果集,targets下内容是标签(比如for each in targets
中的each是标签),标签下的text
之类的属性才是str
;而结果集下没有text属性,标签下才有
注2:
返回的结果集下假如有多个<a>标签,那么索引(targets.a)只能返回第一个<a>标签的结果,此时如果想要所有的<a>标签,可以在下面继续用find_all()
嵌套索引,举例:targets.find_all('a')
(5)获取内容
接着(2),当取得targets
后因为内容是一大堆<a>
标签和对应内容,如果我们想要<a>的内容的话就:
for each in targets:
print(each.text)
如果想要某个属性,比如src
的话就可以:
for each in targets:
print(each.get("src"))
(6)select('查找内容')
①如果是查找标签,直接标签名
就行,例如:soup.select('title')
②如果是查找id
则前面加#
,即soup.select('#id名')
③如果查找class
则前面加.
,例如:soup.select('.List-header')
④如果要获取标签某个属性值则到标签下加['属性名']
就好了,例如:soup.select('a')[0]['href']
,则获取第一个a标签下的href
⑤如果要层层迭代,就空格
隔开,比如要获得id为abc下被div修饰的p标签:soup.select('#abc div p')
注:
最终返回的是个列表,列表下是标签,标签下是str
综合简单示例
import requests
import bs4
url = "https://movie.douban.com/top250"
header = {'user-agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36'}
res = requests.get(url, headers=header) #引入报头,如果get方法请求参数:params=payload
soup = bs4.BeautifulSoup(res.text, "html.parser")
#将res连接得到的内容转文本形式给soup,解析器用默认的parser
targets = soup.find_all("div", class_="hd")
#找到想定位的地方,这里是一个div标签,class属性值是hd
#因为class在python中是类,所以非类的class用class_代替
for each in targets:
print(each.a.span.text) #输出div标签下的a标签的span标签里的文本内容
3.pyquery
其和bs4
功能差不多,同作为CSS
解析器,而且其语法几乎和jquery
相同,所以有时候更为方便
4.requests_html
是requests模块的作者又开发出来的一个新模块,相当于requests
和bs4
的结合,并且还多了JS编译等功能,详细可以参考:
https://segmentfault.com/a/1190000015641160