前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Day20内建模块itertools&urllib

Day20内建模块itertools&urllib

作者头像
林清猫耳
发布2018-04-26 15:37:04
5830
发布2018-04-26 15:37:04
举报
文章被收录于专栏:我爱编程

itertools

Python的内建模块itertools提供了非常有用的用于操作迭代对象的函数。 首先,我们看看itertools提供的几个“无限”迭代器:

代码语言:javascript
复制
>>> import itertools
>>> natuals = itertools.count(1)
>>> for n in natuals:
...     print(n)
...
1
2
3
...

因为count()会创建一个无限的迭代器,所以上述代码会打印出自然数序列,根本停不下来,只能按Ctrl+C退出。 cycle()会把传入的一个序列无限重复下去:

代码语言:javascript
复制
>>> cs = itertools.cycle('ABC')
>>> for c in cs:
    print(c)

    
A
B
C
A
B
C
A
B
C
A
B
C
A

同样停不下来。 repeat()负责把一个元素无限重复下去,不过如果提供第二个参数就可以限定重复次数:

代码语言:javascript
复制
>>> ns = itertools.repeat('ABC',3)
>>> for n in ns:
    print(n)

    
ABC
ABC
ABC

无限序列只有在for迭代时才会无限地迭代下去,如果只是创建了一个迭代对象,它不会事先把无限个元素生成出来,事实上也不可能在内存中创建无限多个元素。 无限序列虽然可以无限迭代下去,但是通常我们会通过takewhile()等函数根据条件判断来截取出一个有限的序列:

代码语言:javascript
复制
>>> natuals = itertools.count(1)
>>> ns = itertools.takewhile(lambda x: x <= 10, natuals)
>>> list(ns)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

tertools提供的几个迭代器操作函数更加有用:

chain()

chain()可以把一组迭代对象串联起来,形成一个更大的迭代器:

代码语言:javascript
复制
>>> for c in itertools.chain('ABC','XYZ'):
    print(c)

    
A
B
C
X
Y
Z

groupby()

groupby()把迭代器中相邻的重复元素挑出来放在一起:

代码语言:javascript
复制
>>> for key, group in itertools.groupby('AAABBBCCAAA'):
    print(key,list(group))

    
A ['A', 'A', 'A']
B ['B', 'B', 'B']
C ['C', 'C']
A ['A', 'A', 'A']

实际上挑选规则是通过函数完成的,只要作用于函数的两个元素返回的值相等,这两个元素就被认为是在一组的,而函数返回值作为组的key。如果我们要忽略大小写分组,就可以让元素'A''a'都返回相同的key:

代码语言:javascript
复制
>>> for key, group in itertools.groupby('AaaVVvCcaaA',lambda c:c.upper()):
    print(key,list(group))

    
A ['A', 'a', 'a']
V ['V', 'V', 'v']
C ['C', 'c']
A ['a', 'a', 'A']

计算圆周率:

代码语言:javascript
复制
import itertools, math

def pi(N):
    k = itertools.count(1,2)
    odd = itertools.takewhile(lambda x: x<=(2*N-1), k)
    s,c = 0,0
    for i in odd:
        s += pow(-1,c)*4/i   # 不能拿odd做运算
        # TypeError: unsupported operand type(s) for /: 'int' and 'itertools.takewhile'
        c += 1

    print(s)

contextlib

@contextmanager这个decorator接受一个generator,用yield语句把with ... as var把变量输出出去,然后,with语句就可以正常地工作了 很多时候,我们希望在某段代码执行前后自动执行特定代码,也可以用@contextmanager实现。例如:

代码语言:javascript
复制
@contextmanager
def tag(name):
    print("<%s>" % name)
    yield
    print("</%s>" % name)

with tag("h1"):
    print("hello")
    print("world")
代码语言:javascript
复制
<h1>
hello
world
</h1>

代码的执行顺序是:

  1. with语句首先执行yield之前的语句,因此打印出<h1>
  2. yield调用会执行with语句内部的所有语句,因此打印出helloworld
  3. 最后执行yield之后的语句,打印出</h1>。 因此,@contextmanager让我们通过编写generator来简化上下文管理。

@closing

如果一个对象没有实现上下文,我们就不能把它用于with语句。这个时候,可以用closing()来把该对象变为上下文对象。例如,用with语句使用urlopen()

代码语言:javascript
复制
from contextlib import closing
from urllib.request import urlopen

with closing(urlopen('https://www.python.org')) as page:
    for line in page:
        print(line)

closing也是一个经过@contextmanager装饰的generator,这个generator编写起来其实非常简单:

代码语言:javascript
复制
@contextmanager
def closing(thing):
    try:
        yield thing
    finally:
        thing.close()

urllib

Get

urllib的request模块可以非常方便地抓取URL内容,也就是发送一个GET请求到指定的页面,然后返回HTTP的响应: 例如,对豆瓣的一个URLhttps://api.douban.com/v2/book/2129650进行抓取,并返回响应:

代码语言:javascript
复制
from urllib import request

with request.urlopen('https://api.douban.com/v2/book/2129650') as f:
    data = f.read()
    print('Status:', f.status, f.reason)
    for k, v in f.getheaders():
        print('%s: %s' % (k, v))
    print('Data:', data.decode('utf-8'))

可以看到HTTP响应的头和JSON数据:

代码语言:javascript
复制
Status: 200 OK
Date: Mon, 09 Apr 2018 13:37:07 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 2118
Connection: close
Vary: Accept-Encoding
X-Ratelimit-Remaining2: 99
X-Ratelimit-Limit2: 100
Expires: Sun, 1 Jan 2006 01:00:00 GMT
Pragma: no-cache
Cache-Control: must-revalidate, no-cache, private
Set-Cookie: bid=aChjmJyJET0; Expires=Tue, 09-Apr-19 13:37:07 GMT; Domain=.douban.com; Path=/
X-DOUBAN-NEWBID: aChjmJyJET0
X-DAE-Node: sindar14d
X-DAE-App: book
Server: dae
Data: {"rating":{"max":10,"numRaters":16,"average":"7.4","min":0},"subtitle":"","author":["廖雪峰"],"pubdate":"2007", ...}

如果我们要想模拟浏览器发送GET请求,就需要使用Request对象,通过往Request对象添加HTTP头,我们就可以把请求伪装成浏览器。例如,模拟iPhone 6去请求豆瓣首页:

代码语言:javascript
复制
from urllib import request

req = request.Request('http://www.douban.com/')
req.add_header('User-Agent', 'Mozilla/6.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/8.0 Mobile/10A5376e Safari/8536.25')
with request.urlopen(req) as f:
    print('Status:', f.status, f.reason)
    for k, v in f.getheaders():
        print('%s: %s' % (k, v))
    print('Data:', f.read().decode('utf-8'))

这样豆瓣会返回适合iPhone的移动版网页:

代码语言:javascript
复制
...
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0">
    <meta name="format-detection" content="telephone=no">
    <link rel="apple-touch-icon" sizes="57x57" href="http://img4.douban.com/pics/cardkit/launcher/57.png" />
...

Post

如果要以POST发送一个请求,只需要把参数data以bytes形式传入。 我们模拟一个微博登录,先读取登录的邮箱和口令,然后按照weibo.cn的登录页的格式以username=xxx&password=xxx的编码传入:

代码语言:javascript
复制
from urllib import request, parse

print('Login to weibo.cn...')
email = input('Email: ')
passwd = input('Password: ')
login_data = parse.urlencode([
    ('username', email),
    ('password', passwd),
    ('entry', 'mweibo'),
    ('client_id', ''),
    ('savestate', '1'),
    ('ec', ''),
    ('pagerefer', 'https://passport.weibo.cn/signin/welcome?entry=mweibo&r=http%3A%2F%2Fm.weibo.cn%2F')
])

req = request.Request('https://passport.weibo.cn/sso/login')
req.add_header('Origin', 'https://passport.weibo.cn')
req.add_header('User-Agent', 'Mozilla/6.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/8.0 Mobile/10A5376e Safari/8536.25')
req.add_header('Referer', 'https://passport.weibo.cn/signin/login?entry=mweibo&res=wel&wm=3349&r=http%3A%2F%2Fm.weibo.cn%2F')

with request.urlopen(req, data=login_data.encode('utf-8')) as f:
    print('Status:', f.status, f.reason)
    for k, v in f.getheaders():
        print('%s: %s' % (k, v))
    print('Data:', f.read().decode('utf-8'))

如果登录成功,我们获得的响应如下:

代码语言:javascript
复制
Status: 200 OK
Server: nginx/1.2.0
...
Set-Cookie: SSOLoginState=1432620126; path=/; domain=weibo.cn
...
Data: {"retcode":20000000,"msg":"","data":{...,"uid":"1658384301"}}

如果登录失败,我们获得的响应如下:

代码语言:javascript
复制
...
Data: {"retcode":50011015,"msg":"\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef","data":{"username":"example@python.org","errline":536}}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018.04.09 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • itertools
    • chain()
      • groupby()
      • contextlib
        • @closing
        • urllib
          • Get
            • Post
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档