Python3.6爬虫记录

Python2和Python3的区别

这里有个网站可以参考,下面都是实战小Demo

在Python3中包urllib2归入了urllib中,所以要导入urllib.request,并且要把urllib2替换成urllib.request

# python2
import urllib2

url = '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()
# python3
import urllib.request

url = '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 实例的参数。

  1. data(默认空):是伴随 url 提交的数据(比如要post的数据),同时 HTTP 请求将从 "GET"方式 改为 "POST"方式。

  2. 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, parse
import ssl

# 基本访问
# 忽略https校验
ssl._create_default_https_context = ssl._create_unverified_context

data = 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'))

 

Get

import urllib.request
import urllib.parse
import 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 res

def 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)

POST

参数都可以通过Charles抓包得到

import urllib.request
import urllib.parse
import ssl
import hashlib
import time
import random
import 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 键值对形式进行编码。

Ajax

import urllib.parse, urllib.request
import json
import ssl

# 豆瓣ajax爬虫

ssl._create_default_https_context = ssl._create_unverified_context
url = '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网站如:https://www.12306.cn/mormhweb/的时候,会警告用户证书不受信任。(据说 12306 网站证书是自己做的,没有通过CA认证)

Handler处理器 和 自定义Opener

  • 我们之前一直都在使用的urlopen,它是一个特殊的opener(也就是模块帮我们构建好的)。

  • 但是基本的urlopen()方法不支持代理、cookie等其他的HTTP/HTTPS高级功能。所以要支持这些功能:

    1. 使用相关的 Handler处理器 来创建特定功能的处理器对象;
    2. 然后通过 urllib.request.build_opener()方法使用这些处理器对象,创建自定义opener对象;
    3. 使用自定义的opener对象,调用open()方法发送请求。
  • 如果程序里所有的请求都使用自定义的opener,可以使用urllib.request.install_opener() 将自定义的 opener 对象 定义为 全局opener,表示如果之后凡是调用urlopen,都将使用这个opener(根据自己的需求来选择)

简单的自定义 HTTPHandler

import urllib.request

# 自定义handler

# 构建httphandler  debuglevel=1自动打开debug模式
http_handler =  urllib.request.HTTPHandler(debuglevel=1)


# 通过handerler构建opener
opener = 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'))



ProxyHandler处理器(代理设置)

免费代理模式

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({})

# 创建opner
if 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())
  • 西刺免费代理IP
  • 快代理免费代理
  • Proxy360代理
  • 全网代理IP

私有代理模式

import urllib.request
import 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'})

# 创建opner
opner = 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()

这是用来记录账号和密码进行校验用的

HTTPPasswordMgrWithDefaultRealm()类将创建一个密码管理对象,用来保存 HTTP 请求相关的用户名和密码,主要应用两个场景:

  1. 验证代理授权的用户名和密码 (ProxyBasicAuthHandler())
  2. 验证Web客户端的的用户名和密码 (HTTPBasicAuthHandler())
  3. HTTPPasswordMgrWithDefaultRealm():来保存私密代理的用户密码
  4. ProxyBasicAuthHandler():来处理代理的身份验证。

1.ProxyBasicAuthHandler 该处理器我们不用先,太麻烦,不如我们上面直接拼接到ProxyHandler

2.HTTPBasicAuthHandler处理器(Web客户端授权验证)

import urllib
# 用户名
user = "test"
# 密码
passwd = "123456"
# Web服务器 IP
webserver = "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_handler
opener = urllib.request.build_opener(httpauth_handler)

# 5. 可以选择通过install_opener()方法定义opener为全局opener
urllib.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()

 

Cookies原理和使用

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 urllib
import http.cookiejar

# 1. 构建一个CookieJar对象实例来保存cookie
cookiejar = http.cookiejar.CookieJar()

# 2. 使用HTTPCookieProcessor()来创建cookie处理器对象,参数为CookieJar()对象
cookies_handle = urllib.request.HTTPCookieProcessor(cookiejar)

# 3. 通过 build_opener() 来构建opener
opener = 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下面发送请求的一些基本操作

Request(以上都可以用Request实现)

Requests 唯一的一个非转基因的 Python HTTP 库,人类可以安全享用。

警告:非专业使用其他 HTTP 库会导致危险的副作用,包括:安全缺陷症、冗余代码症、重新发明轮子症、啃文档症、抑郁、头疼、甚至死亡。看着文档操作即可

http://docs.python-requests.org/zh_CN/latest/index.html

 

正则

Python3.6爬虫记录_第1张图片

上面介绍的无非就是爬下来,如何取出我们需要的数据,就需要正则匹配

re 模块的一般使用步骤如下:

  1. 使用 compile() 函数将正则表达式的字符串形式编译为一个 Pattern 对象

  2. 通过 Pattern 对象提供的一系列方法对文本进行匹配查找,获得匹配结果,一个 Match 对象。

  3. 最后使用 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 re

In [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)
 in ()
----> 1 m.group(3)

IndexError: no such group

In [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 以 列表形式 返回全部能匹配的子串给result
for item in result:
    print (item)
运行结果:

123.141593
3.15
import re
pattern = 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()))
执行结果:



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]: m
Out[92]: ['a', 'bb\x07a', 'mm', 'a']

In [93]: m = pa.split(r'a bb\aa;mm;  a')

In [94]: m
Out[94]: ['a', 'bb', 'aa', 'mm', 'a']

In [95]: m = pa.split(r'a bb\aa;mdm;  a')

In [96]: m
Out[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]: m
Out[99]: ['a', 'bb', 'aa', 'mdm', 'a']

这里匹配字符串如果不加r不管转义,那么\a就不是两个字符了,而是当成一个asic码,需要用r来标识不转移,然后再切割

 

sub 方法用于替换。它的使用形式如下:

sub(repl, string[, count])

其中,repl 可以是字符串也可以是一个函数:

  • 如果 repl 是字符串,则会使用 repl 去替换字符串每一个匹配的子串,并返回替换后的字符串,另外,repl 还可以使用 id 的形式来引用分组,但不能使用编号 0;

  • 如果 repl 是函数,这个方法应当只接受一个参数(Match 对象),并返回一个字符串用于替换(返回的字符串中不能再引用分组)。

  • count 用于指定最多替换次数,不指定时全部替换。
import re
p = 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 world
123 hello, 456 hello
hi 123, hi 456
hi 123, hello 456

贪婪模式和非贪婪模式

https://juejin.im/entry/5805dcc85bbb50005b864405

这里的意思是,贪婪模式直接从段尾开始匹配,非贪婪就是正常往下匹配到第一组满足的

  1. 贪婪模式:在整个表达式匹配成功的前提下,尽可能多的匹配 ( * );
  2. 非贪婪模式:在整个表达式匹配成功的前提下,尽可能少的匹配 ( ? );
  3. Python里数量词默认是贪婪的。

示例一 : 源字符串:abbbc

  • 使用贪婪的数量词的正则表达式 ab* ,匹配结果: abbb。

    * 决定了尽可能多匹配 b,所以a后面所有的 b 都出现了。

  • 使用非贪婪的数量词的正则表达式ab*?,匹配结果: a。

    即使前面有 *,但是 ? 决定了尽可能少匹配 b,所以没有 b。

下面这个例子很好诠释了区别

In [1]: import re

In [2]: patnner = re.compile(r'ab*')

In [3]: m = patnner.match('abbbbbbbbbb')

In [4]: m
Out[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]: m
Out[8]: <_sre.SRE_Match object; span=(0, 1), match='a'>

In [9]: m.group()
Out[9]: 'a'

 

Demo1(内涵段子爬取案例 正则)

import re
import urllib.request
import ssl


class 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('(.*?)
', re.S) # 全文检索 返回list textlist = patterner.findall(html) self.dealPage(textlist) # 处理数据 def dealPage(self, htmllist): for con in htmllist: print('正在写入数据数据。。。。。。。') con = str(con).replace('

','').replace('

','').replace('
','').replace('
','').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.'(.*?)

', 非贪婪模式匹配每个段子

3.a+打开文本追加 结果保存在txt文件中即可

 

Demo2(百度贴吧爬取所有图片 lxml xpath)

import urllib.request, urllib.parse
from lxml import etree
import ssl
import requests
import os

class 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走就能根据关键字下载图片了

 

_csrf的作用

先来看加xsrf这个东西的作用,我们之前写DJango的时候,会默认让form表单带上xsrftoken值。这里不过多介绍,网上有很多这样的介绍,例如知乎上的,博客上的,这里介绍了csrf的攻击原理,无非就是攻击网站钓鱼原来的浏览器,通过间接的方式访问原来的网站,获取到原网站的cookie再伪造form表单即可。之前开发iOS的时候,我们登录都是用token来获取的,每个用户登录都会生成一个token来标识登录态,每次带这个token后台会校验是否需要重新登录,这个token就好比后台传给我们保存在本地的,每次请求都会带给后台。这里也是一样,浏览器获取到后台给的token,我们每次提交form表单的时候带上这个token值,让后台校验,确保没有被攻击。攻击网站只能拿到对应的cookie,但是无法拿到表单的token,后台也就无法校验通过,起到了预防的作用。还有一些验证码之类的防范。

     最后,对防御 CSRF 提出两种解决方案:

  1. 在每个表单中包含一个 CSRF Token.
  2. 不将用于认证的 Token 或 Seesion ID 储存在 Cookie 中,而是将其储存在 localStorage 中,在每次发起请求时手动将 Token 添加到请求中。

但是这里有个问题,攻击网站如果写个脚本把源网站的token也扒下来了,然后继续伪造带给后台,这个怎么破???有大神知道的话求指导。

 

Demo3(糗百xpath模糊查询)

#import urllib
import requests
from lxml import etree
import json

page = 1
url = '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

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(Python3学习)