引言
本篇依然是一个记录学习过程以及打卡的一篇博文。
get和post请求
import urllib.request
def send_request():
# 发送指定的url地址请求,返回一个类文件的响应对象
response = urllib.request.urlopen("http://www.baidu.com/")
# 读取响应中内容,获取网页原始编码字符串
html = response.read()
return html
if __name__ == '__main__':
html = send_request()
print(html)
返回的是以byte形式返回的源码。
如果断开网络后,会出现如下错误:
urllib.error.URLError: <urlopen error [WinError 10060] 由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败。>
然后以下问题均出自《python面试宝典》一书,我只是手敲顺便思考了一遍:
问题一:常见的反爬虫和应对方法?
反爬虫简单的说指的是: 网站根据某些因素断定该请求是一个爬虫程序发出的请求,然后对该请求采取一定的制裁措施。
因此反爬虫应该从两方面来探讨:
- 网站如何断定该请求是一个爬虫:请求头、请求频率、IP地址、cookie等(持续更新请求头、cookie、用户cookie池、代理IP池、设置一定的延时)
- 网站如何制裁这个它认为是爬虫的请求(假数据、空数据、不返回响应、验证码、4xx状态码等)
通过Headers反爬虫:
从用户请求的Headers反爬虫是最常见的反爬虫策略。很多网站都会对Headers的User-Agent进行检测,还有一部分网站会对Referer进行检测(一些资源网站的防盗链就是检测Referer)。如果遇到了这类反爬虫机制,可以直接在爬虫中添加Headers,将浏览器的User-Agent复制到爬虫的Headers中;或者将Referer值修改为目标网站域名。对于检测Headers的反爬虫,在爬虫中修改或者添加Headers就能很好的绕过。
基于用户行为反爬虫:
还有一部分网站是通过检测用户行为,例如同一IP短时间内多次访问同一页面,或者同一账户短时间内多次进行相关操作。
大多数网站都是前一种情况,对于这种情况,使用IP代理就可以解决。可以专门写一个爬虫,爬取网上公开的代理IP,检测后全部保存起来,这样的代理IP爬虫经常会用到,最好自己准备一个。有了大量代理ip后可以每请求几次更换一个ip,这在requests或者urllib2中很容易做到,这样就能很容易的绕过第一种反爬虫。
对于第二种情况,可以在每次请求后随机间隔几秒在进行下一次请求,有些有逻辑漏洞的网站,可以通过请求几次,退出登录,重新登录,继续请求来绕过同一账号短时间内不能多次进行相同请求的限制。
动态页面的反爬虫:
未完待续。
正则表达式
昨天一天有事,晚上和大学同学吃了顿火锅,表示还行。好了,废话不多说,进入正题。
HTTP的请求解析
在上一节我们提到了get请求和post请求,但我并没有解释关于其中的方式与特点,主要是当时看别人都打卡了,然后自己也不想看,就过了,今天比较难得的一个周六,那么就让我们来复习一下吧。内容参考HTTP请求行、请求头、请求体详解
客户端发送一个HTTP请求到服务器的请求消息,包括以下格式:请求行
、请求头部
、空行
、请求数据
。这四个共同构成了HTTP的请求消息结构,然后这里我上面参考的网站里有一张汇总的图,这里我就不再引述了,主要是要知道各种基本的结构解释。那么我们来看如下图:
上述是基于post请求产生的请求报文,get方式的布局基本和其一致,它们是两种最常见的HTTP方法,除了它们还有DELETE、HEAD、OPTIONS、PUT、TRACE、CONNECT方式,那么下面通过表格我们可以来认识各自的含义:
序号 | 方法 | 描述 |
---|---|---|
1 | GET | 请求指定的页面信息,并返回实体主体 |
2 | POST | 向指定资源请求数据进行处理请求(例如提交表单或者上传文件),数据被包含在请求体重,POST请求可能会导致新的资源的简历或已有资源的修改。 |
3 | HEAD | 类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头 |
4 | PUT | 从客户端向服务器传送的数据取代指定的文档的内容 |
5 | DELETE | 请求服务器删除指定的页面 |
6 | CONNECT | HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器 |
7 | OPTIONS | 允许客户端查看服务器的性能 |
8 | TRACE | 回显服务器收到的请求,主要用于测试或诊断 |
HTTP中GET和POST请求的异同:
- GET是从服务器上获取指定页面信息,POST是向服务器提交数据并获取页面信息。
- GET请求参数都显示在URL上,服务器根该请求所包含URL中的QueryString参数来产生响应内容。“GET”请求的参数是URL的一部分。
- POST请求参数在请求体Formdata中,消息长度没有限制而且以隐式的方式进行发送,通常用来向HTTP服务器提交量比较大的数据(比如请求中包含许多参数或者文件上传操作等)。“POST”请求的参数不再URL中,而在请求体中。
Http 1.1支持的请求报头: (参考自文章 HTTP 1.1的常用请求报头)
-
Accept(传输文件类型): 指定浏览器或其他客户端程序所能处理的MIME类型(IE5和6在重新载入页面时,发送的Accept报头不正确,但在最初的请求中是正确的)。
举例:
Accept: /:表示什么都可以接收。Accept:image/gif:表明客户端希望接受GIF图像格式的资源;
Accept:text/html:表明客户端希望接受html文本。
Accept: text/html, application/xhtml+xml;q=0.9, image/*;q=0.8:表示浏览器支持的 MIME 类型分别是 html文本、xhtml和xml文档、所有的图像格式资源。
-
Accept-Charset(字符编码): 标明浏览器可以使用的字符集(如ISO-8859-1).
-
Accept-Encoding(文件编解码格式): 详细列出客户端能处理的编码类型(gzip,compress是两种常见的值),一般来讲花在解码上的时间要远远小于传输的开销。
-
Accept-Language(语言种类): 在servlet能够以多种语言生产结果时,列出客户程序首选的语言。这个报头的值应该是标准语言代码的一种,比如en, en-us, da等。
-
Authorization(认证): 在访问密码保护的WEB页面时,客户用这个报头来识别自己的身份。(Https)
-
Connection(连接类型): 标明客户是否能够处理持续性HTTP连接。持续性(Keep-Alive)是默认的选项。
-
Content-Length(POST数据大小): 只适用于POST请求,用来给定POST数据的大小,以字节为单位。request.getContentlength()方法得到这个报头。
-
Content-Type(POST数据类型):
Content-Type:POST请求里用来表示的内容类型。
举例:Content-Type = Text/XML; charset=gb2312:
指明该请求的消息体中包含的是纯文本的XML类型的数据,字符编码采用“gb2312”。 -
Cookie(“曲奇”,产生记录): 这个报头向服务器返回cookie,这些cookie是之前服务器发送给浏览器的。使用request.getCookies读取这个报头。
-
Host(主机和端口号): 在HTTP1.1中,浏览器和其他客户端程序需要制定这个报头,它标明原始URL中给出的主机名和端口号。
-
If-Modified-Since/If-Unmodified-Since: 这个报头标明,仅当页面在指定日期之后发生更改的情况下,客户程序才希望获取该页面。否则反之为Unmodified,表示未修改或不可改变,即文档比指定的日期要旧才能继续操作。
-
Referer(页面跳转来源): 标明引用Web页面的URL。例如,WEB1单击指向WEB2的链接,在浏览器请求WEB2页面是会把Referer指定为WEB1的URL。
referer还有一个功能就是做防盗链,即我们在查询相应的一些信息如文件、图片等,需要对应的referer,比如说拉钩网的招聘信息,它会判断是否是从主页进来的,否则会拒绝请求。 -
Use-Agent(浏览器名称): 标识生产请求的浏览器或其他客户端程序,根据这个报头,可以针对不同类型的浏览器返回不同的内容。
-
Upgrade-Insecure-Requests(升级为HTTPS请求): Upgrade-Insecure-Requests:升级不安全的请求,意思是会在加载 http 资源时自动替换成 https 请求,让浏览器不再显示https页面中的http请求警报。
HTTP响应状态码:
HTTP常见的响应报头和请求报头差不多,而在爬虫中,其实作用不是太大,而比较重要的就是HTTP的错误状态码了,有时候我们的错误就是根据状态码先确定一个大致的范围,才能节省更多的时间改正BUG。这里我做了一个记录,没有再做整理了,比较全面的一个HTTP状态码见Wiki:
https://zh.wikipedia.org/wiki/HTTP状态码
然后收集了几张比较好的图片,在这里分享一下:
运用正则爬取豆瓣信息
关于正则表达式,这里我就不再多说了,它的底层研究了半小时,发现没看懂然后就略过吧。。几个月前复习的时候画的思维导图如下:
那么我们可以直接来看问题,需要我们用正则表达式爬取豆瓣电影 Top 250里的内容包括名次、影片名称、国家、导演等字段,首先我们需要去分析网页,那么就进入豆瓣的官网,然后找到排行版里按F12进入开发者模式,然后再选定关于显示导演电影等信息的标签,然后首先得到了该网站是动态的:
http://movie.douban.com/top250?start=(page_num)&filter=
然后找到了相关的标签为:
这里如果单单是爬取前250条,应该还不用考虑反爬,参考代码为利用Requests库和正则表达式爬取豆瓣影评Top250
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import requests
import re
import json
from requests.exceptions import RequestException
#得到网页源代码
def get_one_page(url):
try:
res=requests.get(url)
if res.status_code==200:
return res.text
return None
except RequestException:
return None
#对源代码进行解析将要获取的数据存储到dict当中去然后写入本地文件
def parse_one_html(html):
regex='<em class="">(\d+)</em>.*?<span class="title">(.*?)</span>.*?<p class="">(.*?)</p>.*?<span class="rating_num" property="v:average">(.*?)</span>.*?<span>(.*?)</span>.*?<span class="inq">(.*?)</span>'
pattern=re.compile(regex,re.S)
items=re.findall(pattern,html)
#对得到的list列表进行处理,将每条记录变成一个dict
for item in items:
content=""
for every_list in item[2].split():
content=content+"".join(every_list)
content=re.sub(' ',' ',content)
content=re.sub('<br>', '', content)
#将获取到的信息存储到dict字典当中
dict={
"number":item[0],
"name":item[1],
"describe":content,
"star":item[3],
"evaluate":item[4],
"title":item[5]
}
#将得到的dict写入本地文件当中去
with open('result.txt','a',encoding='utf-8') as f:
f.write(json.dumps(dict,ensure_ascii=False)+'\n')
def main(start):
url='https://movie.douban.com/top250'
if start!=0:
url='https://movie.douban.com/top250?start='+str(start)+'&filter='
html=get_one_page(url)
parse_one_html(html)
if __name__=='__main__':
for i in range(10):
main(i*25)