1.爬虫基础
(非原创)
什么是网络爬虫:
- 通俗理解:爬虫是一个模拟人类请求网站行为的程序。可以自动请求网页、并数据抓取下来,然后使用一定的规则提取有价值的数据。
- 专业介绍:百度百科。
通用爬虫和聚焦爬虫:
- 通用爬虫:通用爬虫是搜索引擎抓取系统(百度、谷歌、搜狗等)的重要组成部分。主要是将互联网上的网页下载到本地,形成一个互联网内容的镜像备份。
- 聚焦爬虫:是面向特定需求的一种网络爬虫程序,他与通用爬虫的区别在于:聚焦爬虫在实施网页抓取的时候会对内容进行筛选和处理,尽量保证只抓取与需求相关的网页信息。
为什么用Python写爬虫程序:
- PHP:PHP是世界是最好的语言,但他天生不是做这个的,而且对多线程、异步支持不是很好,并发处理能力弱。爬虫是工具性程序,对速度和效率要求比较高。
- Java:生态圈很完善,是Python爬虫最大的竞争对手。但是Java语言本身很笨重,代码量很大。重构成本比较高,任何修改会导致代码大量改动。爬虫经常要修改采集代码。
- C/C++:运行效率是无敌的。但是学习和开发成本高。写个小爬虫程序可能要大半天时间。
- Python:语法优美、代码简洁、开发效率高、支持的模块多。相关的HTTP请求模块和HTML解析模块非常丰富。还有Scrapy和Scrapy-redis框架让我们开发爬虫变得异常简单。
什么是http和https协议:
HTTP协议:全称是HyperText Transfer Protocol
,中文意思是超文本传输协议,是一种发布和接收HTML页面的方法。服务器端口号是80
端口。 HTTPS协议:是HTTP协议的加密版本,在HTTP下加入了SSL层。服务器端口号是443
端口。
在浏览器中发送一个http请求的过程:
- 当用户在浏览器的地址栏中输入一个URL并按回车键之后,浏览器会向HTTP服务器发送HTTP请求。HTTP请求主要分为“Get”和“Post”两种方法。
- 当我们在浏览器输入URL http://www.baidu.com 的时候,浏览器发送一个Request请求去获取 http://www.baidu.com 的html文件,服务器把Response文件对象发送回给浏览器。
- 浏览器分析Response中的 HTML,发现其中引用了很多其他文件,比如Images文件,CSS文件,JS文件。 浏览器会自动再次发送Request去获取图片,CSS文件,或者JS文件。
- 当所有的文件都下载成功后,网页会根据HTML语法结构,完整的显示出来了。
URL
是Uniform Resource Locator
的简写,统一资源定位符。 一个URL
由以下几部分组成:
scheme://host:port/path/?query-string=xxx#anchor
- scheme:代表的是访问的协议,一般为
http
或者https
以及ftp
等。 - host:主机名,域名,比如
www.baidu.com
。 - port:端口号。当你访问一个网站的时候,浏览器默认使用80端口。
- path:查找路径。比如:
www.jianshu.com/trending/now
,后面的trending/now
就是path
。 - query-string:查询字符串,比如:
www.baidu.com/s?wd=python
,后面的wd=python
就是查询字符串。 - anchor:锚点,后台一般不用管,前端用来做页面定位的。
在浏览器中请求一个url
,浏览器会对这个url进行一个编码。除英文字母,数字和部分符号外,其他的全部使用百分号+十六进制码值进行编码。
常用的请求方法:
在Http
协议中,定义了八种请求方法。这里介绍两种常用的请求方法,分别是get
请求和post
请求。
get
请求:一般情况下,只从服务器获取数据下来,并不会对服务器资源产生任何影响的时候会使用get
请求。post
请求:向服务器发送数据(登录)、上传文件等,会对服务器资源产生影响的时候会使用post
请求。 以上是在网站开发中常用的两种方法。并且一般情况下都会遵循使用的原则。但是有的网站和服务器为了做反爬虫机制,也经常会不按常理出牌,有可能一个应该使用get
方法的请求就一定要改成post
请求,这个要视情况而定。
请求头常见参数:
在http
协议中,向服务器发送一个请求,数据分为三部分,第一个是把数据放在url中,第二个是把数据放在body
中(在post
请求中),第三个就是把数据放在head
中。这里介绍在网络爬虫中经常会用到的一些请求头参数:
User-Agent
:浏览器名称。这个在网络爬虫中经常会被使用到。请求一个网页的时候,服务器通过这个参数就可以知道这个请求是由哪种浏览器发送的。如果我们是通过爬虫发送请求,那么我们的User-Agent
就是Python
,这对于那些有反爬虫机制的网站来说,可以轻易的判断你这个请求是爬虫。因此我们要经常设置这个值为一些浏览器的值,来伪装我们的爬虫。Referer
:表明当前这个请求是从哪个url
过来的。这个一般也可以用来做反爬虫技术。如果不是从指定页面过来的,那么就不做相关的响应。Cookie
:http
协议是无状态的。也就是同一个人发送了两次请求,服务器没有能力知道这两个请求是否来自同一个人。因此这时候就用cookie
来做标识。一般如果想要做登录后才能访问的网站,那么就需要发送cookie
信息了。
常见响应状态码:
200
:请求正常,服务器正常的返回数据。301
:永久重定向。比如在访问www.jingdong.com
的时候会重定向到www.jd.com
。302
:临时重定向。比如在访问一个需要登录的页面的时候,而此时没有登录,那么就会重定向到登录页面。400
:请求的url
在服务器上找不到。换句话说就是请求url
错误。403
:服务器拒绝访问,权限不够。- 404:找不到该页面
500
:服务器内部错误。可能是服务器出现bug
了。- 503:服务器由于维护或者负载过重未能应答,在响应中可能可能会携带Retry-After响应头;有可能是因为爬虫频繁访问url,使服务器忽视爬虫的请求,最终返回503响应状态码
urllib库
urllib
库是Python
中一个最基本的网络请求库。可以模拟浏览器的行为,向指定的服务器发送一个请求,并可以保存服务器返回的数据。
urlopen函数:
在Python3
的urllib
库中,所有和网络请求相关的方法,都被集到urllib.request
模块下面了,以先来看下urlopen
函数基本的使用:
from urllib import request resp = request.urlopen('http://www.baidu.com') print(resp.read())
实际上,使用浏览器访问百度,右键查看源代码。你会发现,跟我们刚才打印出来的数据是一模一样的。也就是说,上面的三行代码就已经帮我们把百度的首页的全部代码爬下来了。一个基本的url请求对应的python代码真的非常简单。
以下对urlopen
函数的进行详细讲解:
url
:请求的url。data
:请求的data
,如果设置了这个值,那么将变成post
请求。- 返回值:返回值是一个
http.client.HTTPResponse
对象,这个对象是一个类文件句柄对象。有read(size)
、readline
、readlines
以及getcode
等方法。
urlretrieve函数:
这个函数可以方便的将网页上的一个文件保存到本地。以下代码可以非常方便的将百度的首页下载到本地:
from urllib import request request.urlretrieve('http://www.baidu.com/','baidu.html')
urlencode函数:
用浏览器发送请求的时候,如果url中包含了中文或者其他特殊字符,那么浏览器会自动的给我们进行编码。而如果使用代码发送请求,那么就必须手动的进行编码,这时候就应该使用urlencode
函数来实现。urlencode
可以把字典数据转换为URL
编码的数据。示例代码如下:
from urllib import parse data = {'name':'爬虫基础','greet':'hello world','age':18} qs = parse.urlencode(data) print(qs)
parse_qs函数:
可以将经过编码后的url参数进行解码。示例代码如下:
from urllib import parse qs = "name=%E7%88%AC%E8%99%AB%E5%9F%BA%E7%A1%80&greet=hello+world&age=100" print(parse.parse_qs(qs))
urlparse和urlsplit:
有时候拿到一个url,想要对这个url中的各个组成部分进行分割,那么这时候就可以使用urlparse
或者是urlsplit
来进行分割。示例代码如下:
from urllib import request,parse url = 'http://www.baidu.com/s?username=zhiliao' result = parse.urlsplit(url) # result = parse.urlparse(url) print('scheme:',result.scheme) print('netloc:',result.netloc) print('path:',result.path) print('query:',result.query)
urlparse
和urlsplit
基本上是一模一样的。唯一不一样的地方是,urlparse
里面多了一个params
属性,而urlsplit
没有这个params
属性。比如有一个url
为:url = 'http://www.baidu.com/s;hello?wd=python&username=abc#1'
,
那么urlparse
可以获取到hello
,而urlsplit
不可以获取到。url
中的params
也用得比较少。
request.Request类:
如果想要在请求的时候增加一些请求头,那么就必须使用request.Request
类来实现。比如要增加一个User-Agent
,示例代码如下:
from urllib import request headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36' } req = request.Request("http://www.baidu.com/",headers=headers) resp = request.urlopen(req) print(resp.read())
智联招聘爬虫实战作业:
- url链接:https://sou.zhaopin.com/?jl=765&in=10100&kw=python&kt=3
- 要求:能爬取一页的数据就可以了。
# -*- coding:utf-8 -*- from urllib import parse,request # url = 'https://sou.zhaopin.com/?jl=765&in=10100&kw=python&kt=3' url = 'https://fe-api.zhaopin.com/c/i/sou?pageSize=60&cityId=765&industry=10100&workExperience=-1&education=-1&companyType=-1&employmentType=-1&jobWelfareTag=-1&kw=python&kt=3&lastUrlQuery=%7B%22jl%22:%22765%22,%22in%22:%2210100%22,%22kw%22:%22python%22,%22kt%22:%223%22%7D' 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', 'Referer':'https://sou.zhaopin.com/?jl=765&in=10100&kw=python&kt=3' } data = { 'pageSize':60, 'cityId':765, 'industry':10100, 'workExperience':-1, 'education':-1, 'companyType':-1, 'employmentType':-1, 'jobWelfareTag':-1, 'kw':'python', 'kt':3, 'lastUrlQuery':{"jl":"765","in":"10100","kw":"python","kt":"3"} } req = request.Request(url,headers = headers,data=parse.urlencode(data).encode('utf-8'),method="GET") resq = request.urlopen(req) print(resq.read().decode('utf-8'))
ProxyHandler处理器(代理设置)
很多网站会检测某一段时间某个IP的访问次数(通过流量统计,系统日志等),如果访问次数多的不像正常人,它会禁止这个IP的访问。
所以我们可以设置一些代理服务器,每隔一段时间换一个代理,就算IP被禁止,依然可以换个IP继续爬取。
urllib中通过ProxyHandler来设置使用代理服务器,下面代码说明如何使用自定义opener来使用代理:
from urllib import request # 这个是没有使用代理的 # resp = request.urlopen('http://httpbin.org/get') # print(resp.read().decode("utf-8")) # 这个是使用了代理的 handler = request.ProxyHandler({"http":"218.66.161.88:31769"}) opener = request.build_opener(handler) req = request.Request("http://httpbin.org/ip") resp = opener.open(req) print(resp.read())
常用的代理有:
- 西刺免费代理IP:http://www.xicidaili.com/
- 快代理:http://www.kuaidaili.com/
- 代理云:http://www.dailiyun.com/
什么是cookie:
在网站中,http请求是无状态的。也就是说即使第一次和服务器连接后并且登录成功后,第二次请求服务器依然不能知道当前请求是哪个用户。cookie
的出现就是为了解决这个问题,第一次登录后服务器返回一些数据(cookie)给浏览器,然后浏览器保存在本地,当该用户发送第二次请求的时候,就会自动的把上次请求存储的cookie
数据自动的携带给服务器,服务器通过浏览器携带的数据就能判断当前用户是哪个了。cookie
存储的数据量有限,不同的浏览器有不同的存储大小,但一般不超过4KB。因此使用cookie
只能存储一些小量的数据。
cookie的格式:
Set-Cookie: NAME=VALUE;Expires/Max-age=DATE;Path=PATH;Domain=DOMAIN_NAME;SECURE
参数意义:
- NAME:cookie的名字。
- VALUE:cookie的值。
- Expires:cookie的过期时间。
- Path:cookie作用的路径。
- Domain:cookie作用的域名。
- SECURE:是否只在https协议下起作用。
使用cookielib库和HTTPCookieProcessor模拟登录:
Cookie 是指网站服务器为了辨别用户身份和进行Session跟踪,而储存在用户浏览器上的文本文件,Cookie可以保持登录信息到用户下次与服务器的会话。
这里以人人网为例。人人网中,要访问某个人的主页,必须先登录才能访问,登录说白了就是要有cookie信息。那么如果我们想要用代码的方式访问,就必须要有正确的cookie信息才能访问。解决方案有两种,第一种是使用浏览器访问,然后将cookie信息复制下来,放到headers中。示例代码如下:
from urllib import request headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36', 'Cookie': 'anonymid=jacdwz2x-8bjldx; depovince=GW; _r01_=1; _ga=GA1.2.1455063316.1511436360; _gid=GA1.2.862627163.1511436360; wp=1; JSESSIONID=abczwY8ecd4xz8RJcyP-v; jebecookies=d4497791-9d41-4269-9e2b-3858d4989785|||||; ick_login=884e75d4-f361-4cff-94bb-81fe6c42b220; _de=EA5778F44555C091303554EBBEB4676C696BF75400CE19CC; p=61a3c7d0d4b2d1e991095353f83fa2141; first_login_flag=1; ln_uact=970138074@qq.com; ln_hurl=http://hdn.xnimg.cn/photos/hdn121/20170428/1700/main_nhiB_aebd0000854a1986.jpg; t=3dd84a3117737e819dd2c32f1cdb91d01; societyguester=3dd84a3117737e819dd2c32f1cdb91d01; id=443362311; xnsid=169efdc0; loginfrom=syshome; ch_id=10016; jebe_key=9c062f5a-4335-4a91-bf7a-970f8b86a64e%7Ca022c303305d1b2ab6b5089643e4b5de%7C1511449232839%7C1; wp_fold=0' } url = 'http://www.renren.com/880151247/profile' req = request.Request(url,headers=headers) resp = request.urlopen(req) with open('renren.html','w') as fp: fp.write(resp.read().decode('utf-8'))
但是每次在访问需要cookie的页面都要从浏览器中复制cookie比较麻烦。在Python处理Cookie,一般是通过http.cookiejar
模块和urllib模块的HTTPCookieProcessor
处理器类一起使用。http.cookiejar
模块主要作用是提供用于存储cookie的对象。而HTTPCookieProcessor
处理器主要作用是处理这些cookie对象,并构建handler对象。
http.cookiejar模块:
该模块主要的类有CookieJar、FileCookieJar、MozillaCookieJar、LWPCookieJar。这四个类的作用分别如下:
- CookieJar:管理HTTP cookie值、存储HTTP请求生成的cookie、向传出的HTTP请求添加cookie的对象。整个cookie都存储在内存中,对CookieJar实例进行垃圾回收后cookie也将丢失。
- FileCookieJar (filename,delayload=None,policy=None):从CookieJar派生而来,用来创建FileCookieJar实例,检索cookie信息并将cookie存储到文件中。filename是存储cookie的文件名。delayload为True时支持延迟访问访问文件,即只有在需要时才读取文件或在文件中存储数据。
- MozillaCookieJar (filename,delayload=None,policy=None):从FileCookieJar派生而来,创建与Mozilla浏览器 cookies.txt兼容的FileCookieJar实例。
- LWPCookieJar (filename,delayload=None,policy=None):从FileCookieJar派生而来,创建与libwww-perl标准的 Set-Cookie3 文件格式兼容的FileCookieJar实例。
登录人人网:
利用http.cookiejar
和request.HTTPCookieProcessor
登录人人网。相关示例代码如下:
from urllib import request,parse from http.cookiejar import CookieJar headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36' } def get_opener(): cookiejar = CookieJar() handler = request.HTTPCookieProcessor(cookiejar) opener = request.build_opener(handler) return opener def login_renren(opener): data = {"email": "970138074@qq.com", "password": "pythonspider"} data = parse.urlencode(data).encode('utf-8') login_url = "http://www.renren.com/PLogin.do" req = request.Request(login_url, headers=headers, data=data) opener.open(req) def visit_profile(opener): url = 'http://www.renren.com/880151247/profile' req = request.Request(url,headers=headers) resp = opener.open(req) with open('renren.html','w') as fp: fp.write(resp.read().decode("utf-8")) if __name__ == '__main__': opener = get_opener() login_renren(opener) visit_profile(opener)
保存cookie到本地:
保存cookie
到本地,可以使用cookiejar
的save
方法,并且需要指定一个文件名:
from urllib import request from http.cookiejar import MozillaCookieJar cookiejar = MozillaCookieJar("cookie.txt") handler = request.HTTPCookieProcessor(cookiejar) opener = request.build_opener(handler) headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36' } req = request.Request('http://httpbin.org/cookies',headers=headers) resp = opener.open(req) print(resp.read()) cookiejar.save(ignore_discard=True,ignore_expires=True)
从本地加载cookie:
从本地加载cookie
,需要使用cookiejar
的load
方法,并且也需要指定方法:
from urllib import request from http.cookiejar import MozillaCookieJar cookiejar = MozillaCookieJar("cookie.txt") cookiejar.load(ignore_expires=True,ignore_discard=True) handler = request.HTTPCookieProcessor(cookiejar) opener = request.build_opener(handler) headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36' } req = request.Request('http://httpbin.org/cookies',headers=headers) resp = opener.open(req) print(resp.read())
requests库
虽然Python的标准库中 urllib模块已经包含了平常我们使用的大多数功能,但是它的 API 使用起来让人感觉不太好,而 Requests宣传是 “HTTP for Humans”,说明使用更简洁方便。
安装和文档地址:
利用pip
可以非常方便的安装:
pip install requests
中文文档:http://docs.python-requests.org/zh_CN/latest/index.html
github地址:https://github.com/requests/requests
发送GET请求:
-
最简单的发送
get
请求就是通过requests.get
来调用:response = requests.get("http://www.baidu.com/")
-
添加headers和查询参数:
如果想添加 headers,可以传入headers参数来增加请求头中的headers信息。如果要将参数放在url中传递,可以利用 params 参数。相关示例代码如下: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) # 查看响应内容,response.text 返回的是Unicode格式的数据 print(response.text) # 查看响应内容,response.content返回的字节流数据 print(response.content) # 查看完整url地址 print(response.url) # 查看响应头部字符编码 print(response.encoding) # 查看响应码 print(response.status_code)
发送POST请求:
-
最基本的POST请求可以使用
post
方法:response = requests.post("http://www.baidu.com/",data=data)
-
传入data数据:
这时候就不要再使用urlencode
进行编码了,直接传入一个字典进去就可以了。比如请求拉勾网的数据的代码:import requests url = "https://www.lagou.com/jobs/positionAjax.json?city=%E6%B7%B1%E5%9C%B3&needAddtionalResult=false&isSchoolJob=0" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36', 'Referer': 'https://www.lagou.com/jobs/list_python?labelWords=&fromSearch=true&suginput=' } data = { 'first': 'true', 'pn': 1, 'kd': 'python' } resp = requests.post(url,headers=headers,data=data) # 如果是json数据,直接可以调用json方法 print(resp.json())
使用代理:
使用requests
添加代理也非常简单,只要在请求的方法中(比如get
或者post
)传递proxies
参数就可以了。示例代码如下:
import requests url = "http://httpbin.org/get" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36', } proxy = { 'http': '171.14.209.180:27829' } resp = requests.get(url,headers=headers,proxies=proxy) with open('xx.html','w',encoding='utf-8') as fp: fp.write(resp.text)
cookie:
如果在一个响应中包含了cookie
,那么可以利用cookies
属性拿到这个返回的cookie
值:
import requests url = "http://www.renren.com/PLogin.do" data = {"email":"970138074@qq.com",'password':"pythonspider"} resp = requests.get('http://www.baidu.com/') print(resp.cookies) print(resp.cookies.get_dict())
session:
之前使用urllib
库,是可以使用opener
发送多个请求,多个请求之间是可以共享cookie
的。那么如果使用requests
,也要达到共享cookie
的目的,那么可以使用requests
库给我们提供的session
对象。注意,这里的session
不是web开发中的那个session,这个地方只是一个会话的对象而已。还是以登录人人网为例,使用requests
来实现。示例代码如下:
import requests url = "http://www.renren.com/PLogin.do" data = {"email":"970138074@qq.com",'password':"pythonspider"} headers = { 'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36" } # 登录 session = requests.session() session.post(url,data=data,headers=headers) # 访问大鹏个人中心 resp = session.get('http://www.renren.com/880151247/profile') print(resp.text)
处理不信任的SSL证书:
对于那些已经被信任的SSL整数的网站,比如https://hr.tencent.com/,那么使用requests
直接就可以正常的返回响应。示例代码如下:
# -*- coding:utf-8 -*- import requests 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"} resp = requests.get('https://hr.tencent.com/',verify=False,headers=headers) print(resp.content.decode('utf-8'))