写在前面:
这算是我的第一篇博客。我写博客的原因应该会和大部分人一样,为了交流和分享知识,同时也可以结交志同道合的朋友。由于我大四毕设的课题与网络爬虫相关,当时花了不少心思去学这一门技术,虽然不能说算是精通,但也是掌握了一些技巧和方法。同时呢,我当时也整理了很多的笔记,本来是没想着要写进博客里,不过最后还是决定行动了。这些笔记我会以文章的形式来展示出来,编程语言为python,大概5-6篇,其中会有我在进行毕设时遇到的一些问题,我也会一一说明,话不多说,一睹为快。(笔记里的一些网址如果说大家访问不了的话可以尝试挂vpn,每一个网址我都会自己去试验一下的,不会出现访问不了的情况)
文章目录
一、爬虫基本原理
爬虫基本流程
1.发起请求。通过HTTP库向目标站点发起请求,即发送一个Request,请求可以包含额外的headers等信息,等待服务器响应。
2.获取响应内容。如果服务器能正常响应,会得到一个Response,Response的内容便是所要获得的页面内容,类型可能有HTML,JSON字符串,二进制数据(如图片视频)等类型。
3.解析内容。得到的内容可能是HTML,可以用正则表达式、网页解析库进行解析。可能是JSON,可以直接转为JSON对象解析,可能是二进制数据,可以保存或者进一步的处理。
4.保存数据。保存形式多样,可以存为文本,也可以保存至数据库,或者保存特定格式的文件。
Request
1.请求方式。主要有GET、POST两种类型,另外还有HEAD、PUT、DELETE、OPTIONS等。Post信息在URL中无显示,get信息参数全都位于URL中。
2.请求URL。URL全称统一资源定位符,如一个网页文档、一张图片、一个视频等都可以用URL唯一来确定 。
3.请求头。包含请求时的头部信息,如User-Agent、Host、Cookies等信息。
4.请求体。请求时额外携带的数据如表单提交时的表单数据。
Response
1.响应状态。200指正常,301代表跳转,404 not found也是状态码的一种,502以上代表服务器处理错误,诸如此类,想要进一步了解可以自行百度。
2.响应头。如内容类型、内容长度、服务器信息、设置Cookies等等。
3.响应体。最主要部分,包含了请求资源的内容,如网页HTML、图片二进制数据等。
爬虫获取的数据类型:网页文本:如HTML文档、Json格式文本等;图片:获取到的是二进制文件,保存为图片格式,写入时“wb”;视频:同为二进制文件,保存为视频格式即可;其他:只要是能请求到的,都能获取。
数据解析方式:
1.直接处理
2.Json解析
3.正则表达式
4.BeautifulSoup
5.PyQuery
6.XPath
保存数据:
1.文本。纯文本、Json、Xml等。
2.关系型数据库。如MySQL、Oracle、SQL Server等具有结构化表结构形式存储。
3.非关系型数据库。如MongoDB、Redis等Key-Value形式存储。
4.二进制文件。如图片、视频、音频等等直接保存成特定格式即可。
常用语法
import requests #导入requests库
url = "https://m.weibo.cn/"
response=requests.get(url) #发送请求
print(response.text)
#获取源代码,可能会导致乱码建议使用response.content.decode(),
#注意此时获取的源代码是未经js渲染的源码,
#应该可以从审查元素>network>左栏选中m.weibo.cn>response中看到
print(response.headers) #获取请求头
print(response.status_code) #获取状态码
注:Network里的js(JavaScript)、css文件可以改变网页源码中的相应内容,称为渲染,渲染后的代码显示在审查元素Elements里。
那么如何解决渲染问题?
分析Ajax请求、Selenium/WebDriver、splash。 以webdriver例:
from selenium import webdriver
driver = webdriver.Chrome()
driver.get("https://m.weibo.com")
print(driver.page_source) #此时获取的代码与审查元素里的代码基本全一致
二、Urllib
什么是Urllib?
python内置的HTTP请求库
包含:
urllib.request 请求模块
urllib.error 异常处理模块
urllib.parse url解析模块
urllib.robotparser robots.txt解析模块等
详情请见 urllib官方文档
urllib.request:
Get类
import urllib.request
url = "http://www.baidu.com/"
response = urllib.request.urlopen(url) #get类型,接收url地址
print(response.read()) #此时会发现打印内容为bytes类型,需要解码⬇️
print(response.read().decode("utf-8")) #bytes类型,解码为网页源代码
Post类
import urllib.parse
import urllib.request
url_test = "http://httpbin.org/post"
#国内访问httpbin不稳定,有可能存在访问不成功的情况。
#请尝试使用vpn,如若还是无法访问,可先行跳过,后面还会详细讲解post访问。
data = bytes(urllib.parse.urlencode({"word":"hello"}),encoding="utf-8")
#data参数需要为bytes类型
response = urllib.request.urlopen(url_test,data=data)
#加上data参数,即为post请求,不加默认get请求
print(response.read().decode("utf-8")) #打印出很多的关键信息
注:因为post类需要对其传入参数,因此将二进制的数据(字典类型)赋值给data,再由data对urlopen指定参数。成功访问后,最终输出中会出现所传入的字典数据。
Timeout
超时参数的设置
import socket
import urllib.error
import urllib.request
url_test = "http://www.baidu.com/"
try:
response = urllib.request.urlopen(url_test, timeout=0.001)
#超时参数timeout,访问时间超过给定值,执行except语句。默认秒钟
except urllib.error.URLError as e:
if isinstance(e.reason,socket.timeout): #判断出错原因为超时
print("TIMEOUT") #此举为了防止无期限地访问,因此设置超时时间
输出
TIMEOUT
Response
响应的一些基本属性
import urllib.request
url_test = "http://www.baidu.com"
response = urllib.request.urlopen(url_test)
print(type(response)) #response类型
print(response.status) #访问状态码,成功访问一般为200
print(response.getheaders()) #响应头
print(response.getheader("Server")) #获取响应头中特定的内容
urllib.request
import urllib.request
url_test = "http://www.baidu.com"
request = urllib.request.Request(url_test) #创造一个request的对象
response = urllib.request.urlopen(request)
print(response.read().decode("utf-8"))
#这种创建对象的请求方式和之前效果相同,除此之外还可以向对象中添加额外信息
上面这种创建对象的请求方式和之前直接访问(不创建对象)效果相同,除此之外还可以向对象中添加额外信息:
import urllib.parse
import urllib.request
url = "http://httpbin.org/post" #可能会需要vpn进行访问
headers = {"User-Agent":"Mozilla/4.0 (compatible;MSIE 5.5; Windows NT)",
"Host":"httpbin.org"}
dict = {"name":"Orange"}
data = bytes(urllib.parse.urlencode(dict),encoding="utf-8")
req = urllib.request.Request(url=url,data=data,headers=headers,method="POST")
#还可以用add方法添加headers
#req.add_header("Host","httpbin.org")
response = urllib.request.urlopen(req)
print(response.read().decode("utf-8"))
通过以上的方式可以对某一对象(例中为req)添加足够多的信息,再以此对象(req)为参数进行访问,简单明了。
Handler代理
需要特殊场合,可以切换IP,不易被网站识别为爬虫,但是由于我的资源有限,没办法演示出来…也许以后有机会单列一篇。
Cookie
存储在用户本地终端上的加密数据,是可以维持用户登录的信息
import urllib.request
import http.cookiejar
url = "http://www.baidu.com"
cookie = http.cookiejar.CookieJar() #生成一个cookie对象
handler = urllib.request.HTTPCookieProcessor(cookie) #模拟代理
opener = urllib.request.build_opener(handler)
response = opener.open(url) #response获得之后,之前声明的cookie被自动赋值
for item in cookie:
print(item.name+"="+item.value)
输出
BAIDUID=F0364169BF4D6A3E69E0D56EE9D7895D:FG=1
BIDUPSID=F0364169BF4D6A3E69E0D56EE9D7895D
H_PS_PSSID=1455_21126_29523_29521_29098_29567_28837_29221_26350_29458_22159
PSTM=1565864013
delPer=0
BDSVRTM=0
BD_HOME=0
Cookie的文本保存:
import urllib.request
import http.cookiejar
url = "http://www.baidu.com"
filename = "cookie.txt"
cookie = http.cookiejar.MozillaCookieJar(filename) #会生成Mozilla格式的cookie
#cookie = http.cookiejar.LWPCookieJar(filename) 会生成LWP格式的cookie
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open(url)
cookie.save(ignore_discard=True,ignore_expires=True)
运行完成之后就可以在相应文件cookie.txt中看到对应cookie信息。
Cookie的文本读取:
import urllib.request
import http.cookiejar
url = "http://www.baidu.com"
cookie = http.cookiejar.LWPCookieJar()
cookie.load("cookie.txt",ignore_discard=True,ignore_expires=True)
#加载文本文件的Cookie
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open(url)
print(response.read().decode("utf-8"))
urllib.error
存在三种Error—URLError,HTTPError,ContentTooShortError,在urllib.error中会有各类的详细属性说明。
import urllib.request
import urllib.error
try:
response = urllib.request.urlopen("http://caofan.com/index.htm")
#尝试访问不存在的网页
except urllib.error.HTTPError as e:
#如果是HTTPError则执行此行,子类,此例中会执行该行代码,捕捉异常
print(e.reason,e.code,e.headers,sep="\n")
except urllib.error.URLError as e: #如果是URLError则执行此行,父类
print(e.reason)
else:
print("Request Successfully")
输出
Not Found
404
Server: nginx/1.11.4
Date: Thu, 15 Aug 2019 10:17:06 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 169
Connection: close
通过异常还可以判断具体属性值:
import urllib.request
import urllib.error
import socket
try:
response = urllib.request.urlopen("http://www.baidu.com",timeout=0.01)
except urllib.error.URLError as e:
print(type(e.reason))
if isinstance(e.reason,socket.timeout):
print("TIMEOUT")
输出
<class 'socket.timeout'>
TIMEOUT
urllib.parse
**Urlparse:**主要作用是对url网址进行解析,分割等
import urllib.parse
#urllib.parse.urlparse(urlstring,scheme="",allow_fragments=True)
result=urllib.parse.urlparse("http://www.baidu.com/index.html;user?id=5#comment")
print(type(result),result,sep='\n')
输出如下:
<class 'urllib.parse.ParseResult'>
ParseResult(scheme='http', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment')
若原网址中未指定协议类型,则传入scheme参数,也可在输出中看到相应协议:
import urllib.parse
result=urllib.parse.urlparse("www.baidu.com/index.html;user?id=5#comment",scheme="https")
# 原网址中未指定协议类型,则传入scheme参数,也可在输出中看到相应协议
print(result)
输出如下:
ParseResult(scheme='https', netloc='', path='www.baidu.com/index.html', params='user', query='id=5', fragment='comment')
但若原网址中已有指定协议类型,再传入scheme参数,那么scheme参数不会生效:
import urllib.parse
result=urllib.parse.urlparse("http://www.baidu.com/index.html;user?id=5#comment",
scheme="https")
#尽管参数scheme被传递为“https”,但由于域名scheme已有,不会覆盖
print(result)
输出如下:
ParseResult(scheme='http', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment')
将False传递给allow_fragments:
import urllib.parse
result=urllib.parse.urlparse("http://www.baidu.com/index.html;user?id=5#comment",
allow_fragments=False)
print(result)
输出:
ParseResult(scheme='http', netloc='www.baidu.com', path='/index.html', params='user', query='id=5#comment', fragment='') #注意将False传递给allow_fragments后,fragment不再有内容,#comment向前填充
但若query中也为空,则会继续向前填充:
import urllib.parse
result=urllib.parse.urlparse("http://www.baidu.com/index.html;#comment",
allow_fragments=False)
print(result)
输出:
ParseResult(scheme='http', netloc='www.baidu.com', path='/index.html', params='#comment', query='', fragment='') # user?id=5缺失后,#comment继续向前填充
**urlunparse:**提供协议、域名、路径、参数进行网址拼接
data = ["http","www.baidu.com","index.html","user","a=6","comment"]
print(urllib.parse.urlunparse(data))
拼接结果如下
http://www.baidu.com/index.html;user?a=6#comment
Urljoin:
需要我们提供域名或者路径,具体拼接结果以后者域名为基准,后者域名没有的部分,由前者域名进行补充。
import urllib.parse
print(urllib.parse.urljoin("http://www.baidu.com","FAQ.html"))
#直接拼合
print(urllib.parse.urljoin("http://www.baidu.com","https://cuiqingcai.com/FAQ.html"))
#采用后者域名,若后者没有则用前面的url相应部分;若后者有则用后者的部分
print(urllib.parse.urljoin("http://www.baidu.com/about.html","https://cuiqingcai.com/FAQ.html"))
print(urllib.parse.urljoin("http://www.baidu.com/about.html","https://cuiqingcai.com/FAQ.html?question=2"))
print(urllib.parse.urljoin("http://www.baidu.com?wd=abc","https://cuiqingcai.com/index.php"))
print(urllib.parse.urljoin("http://www.baidu.com","?category=2#comment"))
print(urllib.parse.urljoin("www.baidu.com","?category=2#comment"))
print(urllib.parse.urljoin("www.baidu.com#comment","?category=2"))
输出如下:
http://www.baidu.com/FAQ.html
https://cuiqingcai.com/FAQ.html
https://cuiqingcai.com/FAQ.html
https://cuiqingcai.com/FAQ.html?question=2
https://cuiqingcai.com/index.php
http://www.baidu.com?category=2#comment
www.baidu.com?category=2#comment
www.baidu.com?category=2
Urlencode
把字典对象转换为get请求参数
params = {
"name": "germey",
"age": 22
}
base_url = "http://www.baidu.com?"
url = base_url + urllib.parse.urlencode(params)
# urlencode方法能够将参数(字典)形式转化为& = 形式,可直接用于拼接url
#另外,一般url参数会跟在 ? 后面
print(urllib.parse.urlencode(params))
print(url)
输出:
name=germey&age=22
http://www.baidu.com?name=germey&age=22
笔记中的大部分内容都来源于我看的视频教材,而我看的视频教材又以某自学吧为主,因此感兴趣的朋友可以私下联系我视频教材的具体链接。有些代码的输出内容涉及网页源码,由于内容过多,我就没有列出输出,还请谅解^_ 其实urllib库我使用的次数并不多,最多的还是requests库,我将在下一篇详细介绍,有什么问题建议欢迎提出哦!