我决心要提高python编程能力. 在此积累一些牛逼的python使用方法.

高阶函数

func(*args, **kw)

可以接收任何数量的或者有关键字的参数的函数的一般形式.
这个可能不算是”高阶函数”, 但是我先把它放在这里吧

filter()

filter这个单词的意思是过滤器, 顾名思义, filter()可以起到过滤器的作用.

filter(function, sequence)
Parameters:
function: function that tests if each element of a
sequence true or not.
sequence: sequence which needs to be filtered, it can
be sets, lists, tuples, or containers of any iterators.
Returns:
returns an iterator that is already filtered.

实例1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# function that filters vowels 
def fun(variable):
letters = ['a', 'e', 'i', 'o', 'u']
if (variable in letters):
return True
else:
return False

# sequence
sequence = ['g', 'e', 'e', 'j', 'k', 's', 'p', 'r']

# using filter function
filtered = filter(fun, sequence)

print('The filtered letters are:')
for s in filtered:
print(s)

output:

The filtered letters are:
e
e

实例2:

1
2
3
4
5
6
7
8
9
10
# a list contains both even and odd numbers.  
seq = [0, 1, 2, 3, 5, 8, 13]

# result contains odd numbers of the list
result = filter(lambda x: x % 2, seq)
print(list(result))

# result contains even numbers of the list
result = filter(lambda x: x % 2 == 0, seq)
print(list(result))

output:

[1, 3, 5, 13]
[0, 2, 8]

filter的第一个参数为一个 lambda表达式.

map()

个人理解: 通过某种函数规则, 利用传入的(一个或多个)序列生成输出的序列.

map(fun, iter)
Parameters :
fun : It is a function to which map passes each element of given iterable.
iter : It is a iterable which is to be mapped.
NOTE : You can pass one or more iterable to the map() function.
Returns :
Returns a list of the results after applying the given function
to each item of a given iterable (list, tuple etc.)

实例1:

1
2
3
4
5
6
7
def addition(n): 
return n + n

# We double all numbers using map()
numbers = (1, 2, 3, 4)
result = map(addition, numbers)
print(list(result))

output:

{2, 4, 6, 8}

实例2:

1
2
3
4
5
numbers1 = [1, 2, 3] 
numbers2 = [4, 5, 6]

result = map(lambda x, y: x + y, numbers1, numbers2)
print(list(result))

output :

[5, 7, 9]

enumerate()

个人理解: 可以返回可迭代的对象的”键值对”(枚举类对象)

Syntax:
enumerate(iterable, start=0)
Parameters:
Iterable: any object that supports iteration
Start: the index value from which the counter is
to be started, by default it is 0

举例:

1
2
3
>>> L2 = ['hello', 'world', 18, 'apple', None]
>>> [(x,y) for x,y in list(enumerate(L2))]
[(0, 'hello'), (1, 'world'), (2, 18), (3, 'apple'), (4, None)]

lambda表达式

个人理解: lambda表达式 是一种匿名的函数.

Syntax:

1
lambda arguments : expression

lambda表达式等效于函数, 例如下面的代码:

1
2
3
4
5
x = lambda a : a + 10
# 等效于
# def x(a):
# return a+10
print(x(5))

output

15

定义有多个参数的lambda表达式:

1
2
x = lambda a, b, c : a + b + c
print(x(5, 6, 2))

output:

13

装饰器

个人理解: 如果事前已经定义好一个函数f(), 随后想扩展它的功能而又不想修改 f() 的代码, 就可以使用装饰器.

例如, 已经有定义好的函数:

1
2
def f():
print("hello world")

简单装饰器

现在, 想扩展f()的功能, 使其在输出"hello world"前先打印"start".

为了不修改原来的代码, 可定义装饰器函数

1
2
3
4
5
def before(func):
def wrapper(*args, **kw):
print('start')
return func(*args, **kw)
return wrapper

再在f()的定义前使用 @ 语法指定这个装饰器

1
2
3
@before
def f():
print('hello world')

此时调用f(), 输出为:

start
hello world

实际上, 装饰器是一个返回函数的高阶函数.
当调用 f() 时, 因为存在装饰器, 所以并不会直接运行 f() 函数体内的代码, 而是会调用函数 before(f).
所以调用 f() 等效于:

1
2
f = before(f)
f()

而 before 中其实只有一行代码 return wrapper.
所以 f 指向了新的函数 wrapper.
所以 f() 等效于 wrapper().

在 f() 运行后再打印"finish"

1
2
3
4
5
6
7
8
9
10
11
12
13
def before(func):
def wrapper(*args, **kw):
print('start')
result = func(*args, **kw)
print('finish)
return result
return wrapper

@before
def f(*args, **kw):
print('hello world')

f()

在 wrapper 中用变量 result 保存了 func 的返回值, 并在适当时刻返回到函数外.

使用多个装饰器实现上一个功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def before(func):
def wrapper(*args, **kw):
print('start')
return func

return wrapper

def after(func):
def wrapper(*args, **kw):
result = func()()
print('finish')
return result

return wrapper

@after
@before
def f(*args, **kw):
print('hello world')

f()

多个装饰器的执行顺序为由内到外.
等效于:

1
2
f = after(before(f))
f()

使用带有参数的装饰器, 打印"start" + 参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def with_parm(name):
def before(func):
def wrapper(*args, **kw):
print('start', name)
return func(*args, **kw)

return wrapper
return before

@with_parm('yjn')
def f(*args, **kw):
print('hello world')

f()

这里带有参数的装饰器, 本质上是对原有的装饰器的封装.
with_parm('yjn') 返回的便是普通的装饰器(before).
只不过在 with_parm 内通过传入的参数对 before 进行修改.

列表生成式

假如需要一个列表: [1,4,9,16 …., 100], 可以使用哪些语法?

  1. 使用for循环

    1
    2
    3
    a = []
    for x in range(1,11):
    a.append(x**2)
  2. 使用文章开始提到的 map() 函数

    1
    list(map(lambda x:x**2,list(range(1,10))))
  3. 使用列表生成式

    1
    [x**2 for x in range(1,11)]

可以看出列表生成式能极大的简化代码.

此外如果使用两层for, 可以生成全排列.

1
2
>>> [m+n for m in "abc" for n in "yjn"]
['ay', 'aj', 'an', 'by', 'bj', 'bn', 'cy', 'cj', 'cn']

还可以搭配 if 和 else 一起使用:

1
2
3
>>> L1 = ['Hello', 'World', 18, 'Apple', None] 
>>> [x.lower() if isinstance(x,str) else x for x in L1]
['hello', 'world', 18, 'apple', None]

正则表达式

符号 解释 示例 说明
. 匹配任意字符 b.t 可以匹配bat / but / b#t / b1t等
\w 匹配字母/数字/下划线 b\wt 可以匹配bat / b1t / b_t等
但不能匹配b#t
\s 匹配空白字符(包括\r、\n、\t等) love\syou 可以匹配love you
\d 匹配数字 \d\d 可以匹配01 / 23 / 99等
\b 匹配单词的边界 \bThe\b
^ 匹配字符串的开始 ^The 可以匹配The开头的字符串
$ 匹配字符串的结束 .exe$ 可以匹配.exe结尾的字符串
\W 匹配非字母/数字/下划线 b\Wt 可以匹配b#t / b@t等
但不能匹配but / b1t / b_t等
\S 匹配非空白字符 love\Syou 可以匹配love#you等
但不能匹配love you
\D 匹配非数字 \d\D 可以匹配9a / 3# / 0F等
\B 匹配非单词边界 \Bio\B
[] 匹配来自字符集的任意单一字符 [aeiou] 可以匹配任一元音字母字符
[^] 匹配不在字符集中的任意单一字符 [^aeiou] 可以匹配任一非元音字母字符
* 匹配0次或多次 \w*
+ 匹配1次或多次 \w+
? 匹配0次或1次 \w?
{N} 匹配N次 \w{3}
{M,} 匹配至少M次 \w{3,}
{M,N} 匹配至少M次至多N次 \w{3,6}
| 分支 foo|bar 可以匹配foo或者bar
(?#) 注释
(exp) 匹配exp并捕获到自动命名的组中
(? <name>exp) 匹配exp并捕获到名为name的组中
(?:exp) 匹配exp但是不捕获匹配的文本
(?=exp) 匹配exp前面的位置 \b\w+(?=ing) 可以匹配I’m dancing中的danc
(?<=exp) 匹配exp后面的位置 (?<=\bdanc)\w+\b 可以匹配I love dancing and reading中的第一个ing
(?!exp) 匹配后面不是exp的位置
(?<!exp) 匹配前面不是exp的位置
*? 重复任意次,但尽可能少重复 a.*b
a.*?b
将正则表达式应用于aabab,前者会匹配整个字符串aabab,后者会匹配aab和ab两个字符串
+? 重复1次或多次,但尽可能少重复
?? 重复0次或1次,但尽可能少重复
{M,N}? 重复M到N次,但尽可能少重复
{M,}? 重复M次以上,但尽可能少重复

Python提供了re模块来支持正则表达式相关操作,下面是re模块中的核心函数。

函数 说明
compile(pattern, flags=0) 编译正则表达式返回正则表达式对象
match(pattern, string, flags=0) 用正则表达式匹配字符串 成功返回匹配对象 否则返回None
search(pattern, string, flags=0) 搜索字符串中第一次出现正则表达式的模式 成功返回匹配对象 否则返回None
split(pattern, string, maxsplit=0, flags=0) 用正则表达式指定的模式分隔符拆分字符串 返回列表
sub(pattern, repl, string, count=0, flags=0) 用指定的字符串替换原字符串中与正则表达式匹配的模式 可以用count指定替换的次数
fullmatch(pattern, string, flags=0) match函数的完全匹配(从字符串开头到结尾)版本
findall(pattern, string, flags=0) 查找字符串所有与正则表达式匹配的模式 返回字符串的列表
finditer(pattern, string, flags=0) 查找字符串所有与正则表达式匹配的模式 返回一个迭代器
purge() 清除隐式编译的正则表达式的缓存
re.I / re.IGNORECASE 忽略大小写匹配标记
re.M / re.MULTILINE 多行匹配标记

爬虫

Basic

基本爬虫工作流程:

常用标准库和第三方库:

  • 下载数据 - urllib / requests / aiohttp。
  • 解析数据 - re / lxml / beautifulsoup4 / pyquery。
  • 缓存和持久化 - pymysql / sqlalchemy / peewee/ redis / pymongo。
  • 生成数字签名 - hashlib。
  • 序列化和压缩 - pickle / json / zlib。
  • 调度器 - 多进程(multiprocessing) / 多线程(threading)。

当访问https链接时会因为验证SSL证书而产生错误. 解决方法有下面两种:

  • 使用未经验证的上下文

    1
    2
    3
    4
    5
    import ssl

    request = urllib.request.Request(url='...', headers={...})
    context = ssl._create_unverified_context()
    web_page = urllib.request.urlopen(request, context=context)
  • 设置全局性取消证书验证

    1
    2
    3
    import ssl

    ssl._create_default_https_context = ssl._create_unverified_context

request 库

  1. 使用GET请求和POST请求
    • 简单请求页面方式:
1
2
3
4
5
r = requests.get('https://api.github.com/events')
r = requests.post('http://httpbin.org/post', data = {'key':'value'})
r = requests.delete('http://httpbin.org/delete')
r = requests.head('http://httpbin.org/get')
r = requests.options('http://httpbin.org/get')

得到一个名为 r 的 Response 对象.

  1. 参数和请求头

    • 带有参数的请求:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      payload = {'key1': 'value1', 'key2': 'value2'}
      r = requests.get("http://httpbin.org/get", params=payload)
      r = requests.post('http://httpbin.org/post', data = {'key':'value'})
      r = requests.put('http://httpbin.org/put', data = {'key':'value'})

      payload = {'key1': 'value1', 'key2': ['value2', 'value3']}
      r = requests.get('http://httpbin.org/get', params=payload)
      print(r.url)
      # http://httpbin.org/get?key1=value1&key2=value2&key2=value3
+ 发送 Cookie
1
2
3
4
url = 'http://httpbin.org/cookies'
cookies = dict(cookies_are='working')

r = requests.get(url, cookies=cookies)
+ 编写请求头
1
2
3
4
url = 'https://api.github.com/some/endpoint'
headers = {'user-agent': 'my-app/0.0.1'}

r = requests.get(url, headers=headers)
  1. 从 Response 对象中获取信息

    • 获取页面

      1
      2
      3
      r.text       # 自动编码
      r.encoding = 'utf-8'
      r.content # 二进制数据
    • 获取响应状态码

      1
      2
      3
      r = requests.get('http://httpbin.org/get')
      r.status_code
      # 200
    • 获取响应头

      1
      r.headers
    • 获取二进制文件(如图片)

      1
      2
      3
      4
      from PIL import Image
      from io import BytesIO

      i = Image.open(BytesIO(r.content))
    • 获取 JSON

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      import requests

      try:
      r = requests.get('https://api.github.com/events')
      r.json()
      # [{u'repository': {u'open_issues': 0, u'url': 'https://github.com/...
      if not r.status_code == 200:
      raise ValueError()

      except ValueError as e:
      print('error')
+ 获取 Cookie
1
2
r.cookies['example_cookie_name']
# 'example_cookie_value'
+ 获取套接字中的 TCP 报文段
1
2
3
with open(filename, 'wb') as fd:
for chunk in r.iter_content(chunk_size):
fd.write(chunk)

XPath 解析

  • 访问节点

    路径表达式 结果
    bookstore 选取 bookstore 元素的所有子节点。
    /bookstore 选取根元素 bookstore。注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径!
    bookstore/book 选取属于 bookstore 的子元素的所有 book 元素。
    //book 选取所有 book 子元素,而不管它们在文档中的位置。
    bookstore//book 选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。
    //@lang 选取名为 lang 的所有属性。
  • 使用谓词 [ ]:

路径表达式 结果
/bookstore/book[1] 选取属于 bookstore 子元素的第一个 book 元素。
/bookstore/book[last()] 选取属于 bookstore 子元素的最后一个 book 元素。
/bookstore/book[last()-1] 选取属于 bookstore 子元素的倒数第二个 book 元素。
/bookstore/book[position() < 3] 选取最前面的两个属于 bookstore 元素的子元素的 book 元素。
//title[@lang] 选取所有拥有名为 lang 的属性的 title 元素。
//title[@lang=’eng’] 选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。
/bookstore/book[price>35.00] 选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。
/bookstore/book[price>35.00]/title 选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。
  • 选取多个节点:
    路径表达式 结果
    /bookstore/* 选取 bookstore 元素的所有子元素。
    //* 选取文档中的所有元素。
    //title[@*] 选取所有带有属性的 title 元素。
    //book/title | //book/price 选取 book 元素的所有 title 和 price 元素。
    //title | //price 选取文档中的所有 title 和 price 元素。
    /bookstore/book/title | //price 选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。

实例: 获取知乎发现上的问题链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from urllib.parse import urljoin

import re
import requests

from bs4 import BeautifulSoup

def main():
headers = {'user-agent': 'Baiduspider'}
proxies = {
'http': 'http://122.114.31.177:808'
}
base_url = 'https://www.zhihu.com/'
seed_url = urljoin(base_url, 'explore') # https://www.zhihu.com/explore
resp = requests.get(seed_url,
headers=headers,
proxies=proxies # 代理
)
soup = BeautifulSoup(resp.text, 'lxml')
href_regex = re.compile(r'^/question') # 正则匹配 /question
link_set = set()
for a_tag in soup.find_all('a', {'href': href_regex}):
if 'href' in a_tag.attrs:
href = a_tag.attrs['href']
full_url = urljoin(base_url, href)
link_set.add(full_url)
print('Total %d question pages found.' % len(link_set))


if __name__ == '__main__':
main()