本文共 23027 字,大约阅读时间需要 76 分钟。
这里有个网站,下面都是实战小Demo
在Python3中包urllib2归入了urllib中,所以要导入urllib.request,并且要把urllib2替换成urllib.request
# python2import urllib2url = 'http://www.jianshu.com/trending/weekly?page={}'headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36'}request = urllib2.Request(url=url, headers=headers)html = urllib2.urlopen(request)print html.read()
# python3import urllib.requesturl = 'http://www.jianshu.com/trending/weekly?page={}'headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36'}request = urllib.request.Request(url=url, headers=headers)# urllib.request.urlopen(url,data,timeout) :第一个参数url,第二个参数是访问url要传送的数据,第三个参数是设置超时的时间html= urllib.request.urlopen(request)print(html.read())
以上python3最简单的爬虫代码
1.Request
在我们第一个例子里,urlopen()的参数就是一个url地址;
但是如果需要执行更复杂的操作,比如增加HTTP报头,必须创建一个 Request 实例来作为urlopen()的参数;而需要访问的url地址则作为 Request 实例的参数。
data(默认空):是伴随 url 提交的数据(比如要post的数据),同时 HTTP 请求将从 "GET"方式 改为 "POST"方式。
headers(默认空):是一个字典,包含了需要发送的HTTP报头的键值对。
2.User-Agent
浏览器 就是互联网世界上公认被允许的身份,如果我们希望我们的爬虫程序更像一个真实用户,那我们第一步,就是需要伪装成一个被公认的浏览器。用不同的浏览器在发送请求的时候,会有不同的User-Agent头。 urllib默认的User-Agent头为:
Python-urllib/x.y
(x和y是Python主版本和次版本号,例如 Python-urllib/3.6)
在 HTTP Request 中加入特定的 Header,来构造一个完整的HTTP请求消息。
可以通过调用
Request.add_header()
添加/修改一个特定的header 也可以通过调用Request.get_header()
来查看已有的header。
3.url编码
from urllib import request, response, parseimport ssl# 基本访问# 忽略https校验ssl._create_default_https_context = ssl._create_unverified_contextdata = parse.urlencode({'wd':'宓珂璟'})# URL参数编码print(data)# 汉字url编码print(parse.quote('宓珂璟'))# 解码print(parse.unquote(data)) # wd=%E5%AE%93%E7%8F%82%E7%92%9F# %E5%AE%93%E7%8F%82%E7%92%9F# wd=宓珂璟request = request.urlopen('http://tieba.baidu.com')print(request.status) # 状态码print(request.getheaders()) # 请求头print(request.getheader('Content-Type')) # 单个值# print(request.read().decode('utf-8'))
import urllib.requestimport urllib.parseimport ssl# 贴吧get爬虫def loadurls(url,filename): print('正在下载%s........'%filename) header = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1 Safari/605.1.15",} request = urllib.request.Request(url, headers=header) res = urllib.request.urlopen(request).read().decode('utf-8') print('下载完毕%s'%filename) print('*'*50) return resdef writedatas(res, filename): with open(filename, 'w') as f: f.write(res) print(filename + '下载完毕')def tiebaSpider(url, begin, end): for index in range(int(begin), int(end) + 1): page = (index - 1) * 50 filename = str(index) + 'html' print('搜索到第' + str(index) + 'html') fullurl = url + '&pn=' + str(page) res = loadurls(fullurl, filename) writedatas(res, filename) print('谢谢使用')if __name__ == '__main__': ssl._create_default_https_context = ssl._create_unverified_context kw = input('请输入搜索关键字') begin = input('请输入起始页面') end = input('请输入结束页面') url = 'http://tieba.baidu.com/f?' + urllib.parse.urlencode({'kw':kw}) # print(url) # print('%s,%s,%s'%(type(kw),type(begin),type(end))) tiebaSpider(url, begin, end)
参数都可以通过Charles抓包得到
import urllib.requestimport urllib.parseimport sslimport hashlibimport timeimport randomimport json# 有道post翻译url = 'http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule'headers = { "Accept": "application/json, text/javascript, */*; q=0.01", "X-Requested-With": "XMLHttpRequest", "Accept-Language": "zh-cn", "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1 Safari/605.1.15",}key = input('请输入你要翻译的单词:')formdata = { "i": key, "from": "AUTO", "to": "AUTO", "smartresult": "dict", "client": "fanyideskweb", "doctype": "json", "version": "2.1", "keyfrom": "fanyi.web", "action": "FY_BY_REALTIME", "typoResult": "false",}# url参数都要进行转码 而且是二进制data = bytes(urllib.parse.urlencode(formdata), encoding='utf-8')request = urllib.request.Request(url, data=data, headers=headers)response = urllib.request.urlopen(request)line = json.load(response)print(line)print(line.get('translateResult', '')[0][0].get('tgt'))# 请输入你要翻译的单词:my name is lucy i love USA# {'type': 'EN2ZH_CN', 'errorCode': 0, 'elapsedTime': 11, 'translateResult': [[{'src': 'my name is lucy i love USA', 'tgt': '我的名字是露西我爱美国'}]]}# 我的名字是露西我爱美国
Content-Length: xxx
: 是指发送的表单数据长度为xxx,也就是字符个数是xxx个。
X-Requested-With: XMLHttpRequest
:表示Ajax异步请求。
Content-Type: application/x-www-form-urlencoded
: 表示浏览器提交 Web 表单时使用,表单数据会按照 name1=value1&name2=value2 键值对形式进行编码。
import urllib.parse, urllib.requestimport jsonimport ssl# 豆瓣ajax爬虫ssl._create_default_https_context = ssl._create_unverified_contexturl = 'https://movie.douban.com/j/chart/top_list?type=11&interval_id=100%3A90&action='header = { "User-Agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1 Safari/605.1.15"}formdata = { "start":"0", "limit":"20"}# print(urllib.parse.quote(url))# print(urllib.parse.urlencode(formdata))data = bytes(urllib.parse.urlencode(formdata), encoding='utf-8')request = urllib.request.Request(url, headers=header, data=data)response = urllib.request.urlopen(request)with open('movie.json', 'w') as j: j.write(response.read().decode('utf-8'))print(response.read().decode('utf-8'))
处理HTTPS请求 SSL证书验证,上面都有屏蔽掉
现在随处可见 https 开头的网站,urllib可以为 HTTPS 请求验证SSL证书,就像web浏览器一样,如果网站的SSL证书是经过CA认证的,则能够正常访问,如:https://www.baidu.com/
等...
如果SSL证书验证不通过,或者操作系统不信任服务器的安全证书,比如浏览器在访问12306网站如:的时候,会警告用户证书不受信任。(据说 12306 网站证书是自己做的,没有通过CA认证)
我们之前一直都在使用的urlopen,它是一个特殊的opener(也就是模块帮我们构建好的)。
但是基本的urlopen()方法不支持代理、cookie等其他的HTTP/HTTPS高级功能。所以要支持这些功能:
Handler处理器
来创建特定功能的处理器对象;urllib.request.build_opener()
方法使用这些处理器对象,创建自定义opener对象;open()
方法发送请求。如果程序里所有的请求都使用自定义的opener,可以使用urllib.request.install_opener()
将自定义的 opener 对象 定义为 全局opener,表示如果之后凡是调用urlopen,都将使用这个opener(根据自己的需求来选择)
import urllib.request# 自定义handler# 构建httphandler debuglevel=1自动打开debug模式http_handler = urllib.request.HTTPHandler(debuglevel=1)# 通过handerler构建openeropener = urllib.request.build_opener(http_handler)# 请求对象request = urllib.request.Request('http://www.baidu.com')# openner对象 open调用请求response = opener.open(request)print(response.read().decode('utf-8'))
免费代理模式
import urllib.request# 免费共有代理# proxy 代理服务器 抓包# 1、User-Agent 反抓包# 2、Proxy 代理 反抓包#proxy_list = [# {"http" : "124.88.67.81:80"},# {"http" : "124.88.67.81:80"},# {"http" : "124.88.67.81:80"},# {"http" : "124.88.67.81:80"},# {"http" : "124.88.67.81:80"}#]# 随机选择一个代理#proxy = random.choice(proxy_list)# 是否启用代理proxyswitch = False# 代理proxyhandler = urllib.request.ProxyHandler({'http': '122.237.105.144:80'})# 不代理nonhandler = urllib.request.ProxyHandler({})# 创建opnerif proxyswitch: opner = urllib.request.build_opener(proxyhandler)else: opner = urllib.request.build_opener(nonhandler)# 可以直接 使用opner 该方法是局部生效的# opner.open(request)# 如果要让全局opner使用自己创建的代理opner,就需要注入全局 之后就可以用urlopne老方法发送了urllib.request.install_opener(opner)url = 'http://www.baidu.com'headers = {}request = urllib.request.Request(url, headers=headers)response = urllib.request.urlopen(request)print(response.read())
私有代理模式
import urllib.requestimport os# 使用系统变量记录账号密码# ~/.bash_profile 修改# 购买私密代理共有代理name = os.environ.get('proxyname')pwd = os.environ.get('proxypwd')# 代理proxyhandler = urllib.request.ProxyHandler({'http': name + ':' + pwd + '@122.237.105.144:80'})# 创建opneropner = urllib.request.build_opener(proxyhandler)url = 'http://www.baidu.com'headers = {}request = urllib.request.Request(url, headers=headers)response = opner.open(request)print(response.read())
这里我们用系统全局变量来记录账号密码 ~/.bash_profile里面导出
这是用来记录账号和密码进行校验用的
HTTPPasswordMgrWithDefaultRealm()
类将创建一个密码管理对象,用来保存 HTTP 请求相关的用户名和密码,主要应用两个场景:
- 验证代理授权的用户名和密码 (
ProxyBasicAuthHandler()
)- 验证Web客户端的的用户名和密码 (
HTTPBasicAuthHandler()
)HTTPPasswordMgrWithDefaultRealm()
:来保存私密代理的用户密码ProxyBasicAuthHandler()
:来处理代理的身份验证。
1.ProxyBasicAuthHandler 该处理器我们不用先,太麻烦,不如我们上面直接拼接到ProxyHandler
2.HTTPBasicAuthHandler处理器(Web客户端授权验证)
import urllib# 用户名user = "test"# 密码passwd = "123456"# Web服务器 IPwebserver = "http://192.168.1.107"# 1. 构建一个密码管理对象,用来保存需要处理的用户名和密码passwdmgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()# 2. 添加账户信息,第一个参数realm是与远程服务器相关的域信息,一般没人管它都是写None,后面三个参数分别是 Web服务器、用户名、密码passwdmgr.add_password(None, webserver, user, passwd)# 3. 构建一个HTTP基础用户名/密码验证的HTTPBasicAuthHandler处理器对象,参数是创建的密码管理对象httpauth_handler = urllib.request.HTTPBasicAuthHandler(passwdmgr)# 4. 通过 build_opener()方法使用这些代理Handler对象,创建自定义opener对象,参数包括构建的 proxy_handleropener = urllib.request.build_opener(httpauth_handler)# 5. 可以选择通过install_opener()方法定义opener为全局openerurllib.request.install_opener(opener)# 6. 构建 Request对象request = urllib.request.Request("http://192.168.1.107")# 7. 定义opener为全局opener后,可直接使用urlopen()发送请求response = urllib.request.urlopen(request)# 8. 打印响应内容print response.read()
Python3 NameError: name 'cookielib' is not defined
import http.cookiejar 用这个
Cookie 是指某些网站服务器为了辨别用户身份和进行Session跟踪,而储存在用户浏览器上的文本文件,Cookie可以保持登录信息到用户下次与服务器的会话。
HTTP是无状态的面向连接的协议, 为了保持连接状态, 引入了Cookie机制 Cookie是http消息头中的一种属性,包括:
Cookie名字(Name)Cookie的值(Value)Cookie的过期时间(Expires/Max-Age)Cookie作用路径(Path)Cookie所在域名(Domain),使用Cookie进行安全连接(Secure)。前两个参数是Cookie应用的必要条件,另外,还包括Cookie大小(Size,不同浏览器对Cookie个数及大小限制是有差异的)。
Cookie由变量名和值组成,根据 Netscape公司的规定,Cookie格式如下:
Set-Cookie: NAME=VALUE;Expires=DATE;Path=PATH;Domain=DOMAIN_NAME;SECURE
案例一:这里不贴代码了,无非就是我们手动登录某个网站,前提是最简单的网站,登录的时候没有动态token机制。我们登录之后,抓包拿到cookies,然后我们访问登录后的页面,如果代码直接访问,而且把cookies带在请求头上,那么依然是可以访问的,如果没有cookies,就会出现跳到登录页面的情况。这种手动方式获取cookies,下面看下cookies对象自动登录获取存储,下一次再次请求的时候自动使用
import urllibimport http.cookiejar# 1. 构建一个CookieJar对象实例来保存cookiecookiejar = http.cookiejar.CookieJar()# 2. 使用HTTPCookieProcessor()来创建cookie处理器对象,参数为CookieJar()对象cookies_handle = urllib.request.HTTPCookieProcessor(cookiejar)# 3. 通过 build_opener() 来构建openeropener = urllib.request.build_opener(cookies_handle)# 4. addheaders 接受一个列表,里面每个元素都是一个headers信息的元祖, opener将附带headers信息opener.addheaders = [("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")]# 5. 需要登录的账户和密码data = {"email":"xxxx", "password":"xxxx"} # 6. 通过urlencode()转码postdata = urllib.parse.urlencode(data)# 7. 构建Request请求对象,包含需要发送的用户名和密码request = urllib.request.Request("http://www.renren.com/PLogin.do", data = postdata)# 8. 通过opener发送这个请求,并获取登录后的Cookie值,opener.open(request) # 9. opener包含用户登录后的Cookie值,可以直接访问那些登录后才可以访问的页面response = opener.open("http://www.renren.com/410043129/profile") # 10. 打印响应内容print response.read()
以上就是Python3下面发送请求的一些基本操作
Requests 唯一的一个非转基因的 Python HTTP 库,人类可以安全享用。
警告:非专业使用其他 HTTP 库会导致危险的副作用,包括:安全缺陷症、冗余代码症、重新发明轮子症、啃文档症、抑郁、头疼、甚至死亡。看着文档操作即可
上面介绍的无非就是爬下来,如何取出我们需要的数据,就需要正则匹配
re 模块的一般使用步骤如下:
使用 compile()
函数将正则表达式的字符串形式编译为一个 Pattern
对象
通过 Pattern
对象提供的一系列方法对文本进行匹配查找,获得匹配结果,一个 Match 对象。
Match
对象提供的属性和方法获得信息,根据需要进行其他的操作在上面,我们已将一个正则表达式编译成 Pattern 对象,接下来,我们就可以利用 pattern 的一系列方法对文本进行匹配查找了。
Pattern 对象的一些常用方法主要有:
- match 方法:从起始位置开始查找,一次匹配
- search 方法:从任何位置开始查找,一次匹配
- findall 方法:全部匹配,返回列表
- finditer 方法:全部匹配,返回迭代器
- split 方法:分割字符串,返回列表
- sub 方法:替换
match 方法用于查找字符串的头部(也可以指定起始位置),它是一次匹配,只要找到了一个匹配的结果就返回,而不是查找所有匹配的结果。它的一般使用形式如下:
match(string[, pos[, endpos]])
其中,string 是待匹配的字符串,pos 和 endpos 是可选参数,指定字符串的起始和终点位置,默认值分别是 0 和 len (字符串长度)。因此,当你不指定 pos 和 endpos 时,match 方法默认匹配字符串的头部。
当匹配成功时,返回一个 Match 对象,如果没有匹配上,则返回 None。
In [45]: import reIn [46]: pattern = re.compile(r'([a-z]+) ([a-z]+)',re.I)In [47]: m = pattern.match('Hello World Mikejing Python3')In [48]: print(m)<_sre.SRE_Match object; span=(0, 11), match='Hello World'>In [49]: m.groups()Out[49]: ('Hello', 'World')In [50]: m.group()Out[50]: 'Hello World'In [51]: m.group(0)Out[51]: 'Hello World'In [52]: m.group(1)Out[52]: 'Hello'In [53]: m.group(2)Out[53]: 'World'In [54]: m.group(3)---------------------------------------------------------------------------IndexError Traceback (most recent call last)<ipython-input-54-974525c8e304> in <module>()----> 1 m.group(3)IndexError: no such groupIn [55]: m.span()Out[55]: (0, 11)In [56]: m.span(0)Out[56]: (0, 11)In [57]: m.span(1)Out[57]: (0, 5)In [58]: m.span(2)Out[58]: (6, 11)
search 方法
search 方法用于查找字符串的任何位置,它也是一次匹配,只要找到了一个匹配的结果就返回,而不是查找所有匹配的结果,它的一般使用形式如下:
search(string[, pos[, endpos]])
其中,string 是待匹配的字符串,pos 和 endpos 是可选参数,指定字符串的起始和终点位置,默认值分别是 0 和 len (字符串长度)。
当匹配成功时,返回一个 Match 对象,如果没有匹配上,则返回 None。
import re# 将正则表达式编译成 Pattern 对象pattern = re.compile(r'\d+')# 使用 search() 查找匹配的子串,不存在匹配的子串时将返回 None# 这里使用 match() 无法成功匹配m = pattern.search('hello 123456 789')if m: # 使用 Match 获得分组信息 print ('matching string:',m.group()) # 起始位置和结束位置 print ('position:',m.span())
findall 和 finditer
上面的 match 和 search 方法都是一次匹配,只要找到了一个匹配的结果就返回。然而,在大多数时候,我们需要搜索整个字符串,获得所有匹配的结果。
findall 方法的使用形式如下:
findall(string[, pos[, endpos]]) 返回数组
finditer(string[, pos[, endpos]]) 返回可迭代对象
其中,string 是待匹配的字符串,pos 和 endpos 是可选参数,指定字符串的起始和终点位置,默认值分别是 0 和 len (字符串长度)。
findall 以列表形式返回全部能匹配的子串,如果没有匹配,则返回一个空列表。
import re#re模块提供一个方法叫compile模块,提供我们输入一个匹配的规则#然后返回一个pattern实例,我们根据这个规则去匹配字符串pattern = re.compile(r'\d+\.\d*')#通过partten.findall()方法就能够全部匹配到我们得到的字符串result = pattern.findall("123.141593, 'bigcat', 232312, 3.15")#findall 以 列表形式 返回全部能匹配的子串给resultfor item in result: print (item)运行结果:123.1415933.15
import repattern = re.compile(r'\d+')result_iter1 = pattern.finditer('hello 123456 789')result_iter2 = pattern.finditer('one1two2three3four4', 0, 10)print (type(result_iter1))print (type(result_iter2))print ('result1...')for m1 in result_iter1: # m1 是 Match 对象 print ('matching string: {}, position: {}'.format(m1.group(), m1.span()))print ('result2...')for m2 in result_iter2: print ('matching string: {}, position: {}'.format(m2.group(), m2.span()))执行结果:<type 'callable-iterator'><type 'callable-iterator'>result1...matching string: 123456, position: (6, 12)matching string: 789, position: (13, 16)result2...matching string: 1, position: (3, 4)matching string: 2, position: (7, 8)
split 方法
split 方法按照能够匹配的子串将字符串分割后返回列表,它的使用形式如下:
split(string[, maxsplit])
其中,maxsplit 用于指定最大分割次数,不指定将全部分割。
In [90]: pa = re.compile(r'[\s\d\\\;]+')In [91]: m = pa.split('a bb\aa;mm; a')In [92]: mOut[92]: ['a', 'bb\x07a', 'mm', 'a']In [93]: m = pa.split(r'a bb\aa;mm; a')In [94]: mOut[94]: ['a', 'bb', 'aa', 'mm', 'a']In [95]: m = pa.split(r'a bb\aa;mdm; a')In [96]: mOut[96]: ['a', 'bb', 'aa', 'mdm', 'a']In [97]: pa = re.compile('[\s\d\\\;]+')In [98]: m = pa.split(r'a bb\aa;mdm; a')In [99]: mOut[99]: ['a', 'bb', 'aa', 'mdm', 'a']
这里匹配字符串如果不加r不管转义,那么\a就不是两个字符了,而是当成一个asic码,需要用r来标识不转移,然后再切割
sub 方法用于替换。它的使用形式如下:
sub(repl, string[, count])
其中,repl 可以是字符串也可以是一个函数:
如果 repl 是字符串,则会使用 repl 去替换字符串每一个匹配的子串,并返回替换后的字符串,另外,repl 还可以使用 id 的形式来引用分组,但不能使用编号 0;
如果 repl 是函数,这个方法应当只接受一个参数(Match 对象),并返回一个字符串用于替换(返回的字符串中不能再引用分组)。
import rep = re.compile(r'(\w+) (\w+)') # \w = [A-Za-z0-9]s = 'hello 123, hello 456'print p.sub(r'hello world', s) # 使用 'hello world' 替换 'hello 123' 和 'hello 456'print p.sub(r'\2 \1', s) # 引用分组def func(m): return 'hi' + ' ' + m.group(2)print (p.sub(func, s))print (p.sub(func, s, 1)) # 最多替换一次执行结果:hello world, hello world123 hello, 456 hellohi 123, hi 456hi 123, hello 456
这里的意思是,贪婪模式直接从段尾开始匹配,非贪婪就是正常往下匹配到第一组满足的
示例一 : 源字符串:abbbc
ab*
,匹配结果: abbb。
*
决定了尽可能多匹配 b,所以a后面所有的 b 都出现了。
ab*?
,匹配结果: a。 即使前面有
*
,但是?
决定了尽可能少匹配 b,所以没有 b。
下面这个例子很好诠释了区别
In [1]: import reIn [2]: patnner = re.compile(r'ab*')In [3]: m = patnner.match('abbbbbbbbbb')In [4]: mOut[4]: <_sre.SRE_Match object; span=(0, 11), match='abbbbbbbbbb'>In [5]: m.group()Out[5]: 'abbbbbbbbbb'In [6]: patnner = re.compile(r'ab*?')In [7]: m = patnner.match('abbbbbbbbbb')In [8]: mOut[8]: <_sre.SRE_Match object; span=(0, 1), match='a'>In [9]: m.group()Out[9]: 'a'
import reimport urllib.requestimport sslclass Spider: # 初始化 def __init__(self): # 控制页数 self.page = 1 # 控制是否继续爬取 self.switch = True # 请求网页数据 def loadPage(self): print('正在爬取数据。。。。。。') url = 'https://www.neihan8.com/article/list_5_' + str(self.page) + '.html' headers = { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1 Safari/605.1.15" } # request对象 request = urllib.request.Request(url, headers=headers) # 发送 response = urllib.request.urlopen(request) # 不转码打印出来的是二进制的 需要根据网页编码方式转码,一般国际就是utf-8,这里是gbk的 html = response.read().decode('gb2312"') print('爬取数据完毕。。。。。。') # 正则匹配,这里两个div之间获取数据如果用 .*默认是贪婪模式,会从文末匹配出最后一个div,加了?就非贪婪,从头开始匹配满足的第一个 patterner = re.compile('<div\sclass="f18 mb20">(.*?)</div>', re.S) # 全文检索 返回list textlist = patterner.findall(html) self.dealPage(textlist) # 处理数据 def dealPage(self, htmllist): for con in htmllist: print('正在写入数据数据。。。。。。。') con = str(con).replace('<p>','').replace('</p>','').replace('<br>','').replace('<br />','').replace(' ','').replace('\t','') self.writePage(con) print('写入数据数据完毕。。。。。。。') # 写入数据到文本 a+是追加的方式 w是清楚覆盖方式 def writePage(self, content): with open('neihan.txt', 'a+') as f: # f.write('\n') f.write(content) # f.write('\n') f.write('*'*200) # 开始方法 def startSpider(self): print('欢迎进入爬虫小Demo,开始爬虫') while self.switch: self.loadPage() pp = input('如果您要继续爬数据,直接按下回车,如果你不想继续,请输入quit') if pp == 'quit': self.switch = False self.page += 1 print('谢谢使用')if __name__ == '__main__': ssl._create_default_https_context = ssl._create_unverified_context spider = Spider() spider.startSpider()
1.还是用Python3自带的urllib来请求数据
2.'<div\sclass="f18 mb20">(.*?)</div>', 非贪婪模式匹配每个段子
3.a+打开文本追加 结果保存在txt文件中即可
import urllib.request, urllib.parsefrom lxml import etreeimport sslimport requestsimport osclass Spider: def __init__(self): self.startpage = 0 self.endpage = 0 self.keywords = '' self.url = 'https://tieba.baidu.com' self.currentpage = 0 def loadPage(self): data = { 'kw' : self.keywords, 'pn' : self.currentpage } # 参数编码 para = urllib.parse.urlencode(data) url = self.url + '/f?' + para print(url) headers = { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1 Safari/605.1.15" } # 这里如果用urllin不知道什么原因,无法匹配 # 应该是user-agent导致的,如果要用就用ie的 # request = urllib.request.Request(url, headers=headers) # response = urllib.request.urlopen(request) response = requests.get(url, headers) content = etree.HTML(response.text) detainList = content.xpath('//div[@class="t_con cleafix"]/div/div/div/a/@href') print(detainList) for detaillink in detainList: self.loadImage(self.url + detaillink) def loadImage(self, detailurl): print(detailurl) headers = { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1 Safari/605.1.15" } # 请求帖子详情页面 response = requests.get(detailurl, headers) content = etree.HTML(response.text) # 爬出详情页面的图片资源 imageurls = content.xpath("//img[@class='BDE_Image']/@src") for imgurl in imageurls: print(imgurl) self.writeImage(imgurl) def writeImage(self, imageurl): headers = { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1 Safari/605.1.15" } response = requests.get(imageurl, headers) filename = imageurl[-10:] imgfiles = os.getcwd() + '/meinv' if os.path.exists(imgfiles): print('文件夹存在') else: print('不存在,创建') os.mkdir(imgfiles) with open('meinv/' + filename,'wb') as img: img.write(response.content) def startSpider(self): self.keywords = input('请输入需要爬取的贴吧名称:') self.startpage = int(input('请输入起始页:')) self.endpage = int(input('请输入结束页:')) for index in range(self.startpage, self.endpage+1): self.currentpage = (index - 1) * 50 self.loadPage()if __name__ == '__main__': ssl._create_default_https_context = ssl._create_unverified_context spider = Spider() spider.startSpider()
1.需要pip3 install lxml第三方包解析xpath
2.根据网页xpath搜集分析的数据用于匹配xml,首先根据关键字匹配贴吧所有帖子,继续打开贴吧帖子分析详情然后爬出每个详情的图片
3.这里还是用requests算了,urllib我是不能和lxml一起解析,有对应环境的直接copy走就能根据关键字下载图片了
先来看加xsrf这个东西的作用,我们之前写DJango的时候,会默认让form表单带上xsrftoken值。这里不过多介绍,网上有很多这样的介绍,例如,,这里介绍了csrf的攻击原理,无非就是攻击网站钓鱼原来的浏览器,通过间接的方式访问原来的网站,获取到原网站的cookie再伪造form表单即可。之前开发iOS的时候,我们登录都是用token来获取的,每个用户登录都会生成一个token来标识登录态,每次带这个token后台会校验是否需要重新登录,这个token就好比后台传给我们保存在本地的,每次请求都会带给后台。这里也是一样,浏览器获取到后台给的token,我们每次提交form表单的时候带上这个token值,让后台校验,确保没有被攻击。攻击网站只能拿到对应的cookie,但是无法拿到表单的token,后台也就无法校验通过,起到了预防的作用。还有一些验证码之类的防范。
最后,对防御 CSRF 提出两种解决方案:
- 在每个表单中包含一个 CSRF Token.
- 不将用于认证的 Token 或 Seesion ID 储存在 Cookie 中,而是将其储存在 localStorage 中,在每次发起请求时手动将 Token 添加到请求中。
但是这里有个问题,攻击网站如果写个脚本把源网站的token也扒下来了,然后继续伪造带给后台,这个怎么破???有大神知道的话求指导。
#import urllibimport requestsfrom lxml import etreeimport jsonpage = 1url = 'http://www.qiushibaike.com/8hr/page/' + str(page) headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36', 'Accept-Language': 'zh-CN,zh;q=0.8'}try: response = requests.get(url, headers=headers) resHtml = response.text html = etree.HTML(resHtml) result = html.xpath('//div[contains(@id,"qiushi_tag")]') print(len(result)) for site in result: item = {} imgUrl = site.xpath('./div[@class="thumb"]/a/img/@src') imgUrl = imgUrl[0] if len(imgUrl)>0 else '' username = site.xpath('./div/a/@title')[0] content = site.xpath('.//div[@class="content"]/span')[0].text.strip() item = { 'username':username, 'img':imgUrl, 'content':content } print(item) with open('qiushi.json', 'a') as f: f.write(json.dumps(item, ensure_ascii=False) + '\n')except Exception as err: print(err)
1.xpath查询出来都是数组,模糊查询//div[contains(@id,"qiushi_tag") 例如这样查询所有匹配的模块
2.如果获取的不是属性里面的内容,就需要.text获取
3.默认utf-8编码,dumps就不要指定编码方式了,默认的不需要改成False