Dust8 的博客

读书百遍其义自见

0%

最近碰过一个需求,要把 sqlserver 里面的几个字段拼接并做 md5 处理,在插入为一个 guid 字段。由于数据库安装的是中文,用数据库的 HASHBYTES 做 md5 碰到中文会和正常的不一致,因为它是用 gb2312 编码的,正常的应该是用 utf8 编码。后来的网上找到可以用 SQL CLR 用户定义的函数 来实现。

新建项目

新建项目

添加新建项

添加新建项

实现函数

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
32
33
34
35
36
37
38
39
40
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Security.Cryptography;
using System.Text;

public partial class UserDefinedFunctions
{
[Microsoft.SqlServer.Server.SqlFunction]

/// <summary>
/// md5加密
/// </summary>
/// <param name="text"></param>
public static SqlString MD5Encrypt(SqlString text)
{
string hash = string.Empty;
MD5CryptoServiceProvider md5provider = new MD5CryptoServiceProvider();
byte[] bytes = md5provider.ComputeHash(new UTF8Encoding().GetBytes(text.ToString()));
for (int i = 0; i < bytes.Length; i++)
{
hash += bytes[i].ToString("x2");
}
return new SqlString(hash.ToString());
}

/// <summary>
/// 字符串转guid
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
public static SqlString Guid(SqlString text)
{
Guid guid = new Guid(text.ToString());
return new SqlString(guid.ToString());
}

}

生成 dll

注册 dll

注册dll

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
USE temp;
GO

-- 注册自定义函数
CREATE FUNCTION dbo.MD5Encrypt(@src NVARCHAR(4000))
RETURNS NVARCHAR(32)
EXTERNAL NAME SqlCLR.UserDefinedFunctions.MD5Encrypt;
GO

CREATE FUNCTION dbo.Guid(@src NVARCHAR(32))
RETURNS NVARCHAR(50)
external name SqlCLR.UserDefinedFunctions.Guid;

-- 开启clr 支持
EXEC sp_configure 'clr enabled';
EXEC sp_configure 'clr enabled' , '1';
RECONFIGURE;


select dbo.MD5Encrypt('我'),dbo.Guid(dbo.MD5Encrypt('我'))

注

备注:

termux 是 android 下的 Linux 环境和命令行模拟器.

termux 文档

安装 jupyter

基本使用

1
2
3
4
apt update
apt install python python-dev
apt install clang libzmq libzmp-dev libcrypt-dev
pip install jupyter

如果漏装依赖会报错

  • ‘Python.h’ file not found
  • ‘crypt.h’ file not found

安装成功就可以正常使用了.

高级使用

对外使用和设置密码

具体可以看 Running a notebook server
这样就可以在其他电脑连接手机里面的 jupyter, 并设置了密码,地址就不用代 token 了.

使用魔法方法

在 jupyter 里面运行 linux 命令.
jupyter magic

最近有个爬虫项目网站太多,绝大部分接口都是json,有些还有参数加密,
一个个分析接口就太麻烦,就想用浏览器全部渲染出来,就可以省掉这些步骤。
最近流行 headless 就用它了。puppeteer 是 nodejs 的,它的 python
绑定比较好的是 pyppeteer。

基本用法

中文资料非常少,接口看文档,例子看 tests 下面的测试用例。
还可以看看 puppeteer 的教程。

启动参数

1
2
3
4
5
browser = await pyppeteer.launch({
'devtools':
False,
'args': ['--no-sandbox', '--user-agent="' + UserAgent + '"']
})

devtools 控制界面的显示,用来调试。args 是浏览器启动的命令行参数,可以设置浏览器头部,
不然会标示为无头浏览器。--no-sandbox 在 docker 里使用时需要加入的参数,不然会报错。

请求钩子

为了加快渲染速度,可以禁止加载图片之类的。

1
2
3
4
5
6
7
8
9
async def request_check(req):
'''请求过滤'''
if req.resourceType in ['image', 'media', 'eventsource', 'websocket']:
await req.abort()
else:
await req.continue_()

await page.setRequestInterception(True)
page.on('request', request_check)

网络问题

请求加载是否完成,无网都需要处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
async def goto(page, url):
while True:
try:
await page.goto(url, {
'timeout': 0,
'waitUntil': 'networkidle0'
})
break
except (pyppeteer.errors.NetworkError,
pyppeteer.errors.PageError) as ex:
# 无网络 'net::ERR_INTERNET_DISCONNECTED','net::ERR_TUNNEL_CONNECTION_FAILED'
if 'net::' in str(ex):
await asyncio.sleep(10)
else:
raise

注入js文件

比如一些js库,我使用 Ajax-hook 来统计 ajax 的请求完成情况,
需要在网页头部注入js文件,一些自己的库,比较大,也这样注入。

1
2
3
4
5
6
from pathlib import Path

CURDIR = Path(__file__).parent
JS_AJAX_HOOK_LIB = str(CURDIR / 'static' / 'ajaxhook.min.js')

await page.addScriptTag(path=JS_AJAX_HOOK_LIB)

这样注入的js文件不能有中文,因为 pyppeteer 里面打开文件用的是默认编码,可以 hook 住
open 函数来解决。

1
2
# 因为 pyppeteer 库里面 addScriptTag 用的是系统默认编码,导致js文件里面不能有中文
pyppeteer.frame_manager.open = lambda file: open(file, encoding='utf8')

在docker里使用

在 window10 里开发很流程,部署到 windows server 上,可能由于配置比较差或其他原因,网站渲染很慢。
可以放在容器里,效果明显。注意点是上面提到了的关闭沙盒模式,需要下一些浏览器的依赖,还有就是最好先把浏览器下好,做到镜像里,这样
就不会在容器里一个一个下了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FROM python:slim

WORKDIR /usr/src/app

RUN apt-get update && apt-get install -y gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
RUN apt-get install -y vim

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
RUN python -c "import pyppeteer;pyppeteer.chromium_downloader.download_chromium();"


COPY . .

VOLUME /data

socks

socks

SOCKS
requests 从 2.10.0 版本支持 socks 协议.
安装:

1
pip install requests[socks]

使用:

1
2
3
4
proxies = {
'http': 'socks5://user:pass@host:port',
'https': 'socks5://user:pass@host:port'
}

requests 怎么使用 socks 的

首先是要安装额外库

1
pip install requests[socks]

这种安装方法来自 setuptools 的 extras_require . 可以在 requests 的 setup.py 里面看到.

1
2
3
4
5
extras_require={
'security': ['pyOpenSSL>=0.14', 'cryptography>=1.3.4', 'idna>=2.0.0'],
'socks': ['PySocks>=1.5.6, !=1.5.7'],
'socks:sys_platform == "win32" and (python_version == "2.7" or python_version == "2.6")': ['win_inet_pton'],
}

可以看到会安装 PySocks 库.

PySocks 的引入方式是 import socks . 在 requests 里面只在 requests/adapters.py 看到

1
2
3
from urllib3.contrib.socks import SOCKSProxyManager

DEFAULT_POOLSIZE = 10

使用的是 urllib3 库.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class SOCKSProxyManager(PoolManager):
"""
A version of the urllib3 ProxyManager that routes connections via the
defined SOCKS proxy.
"""
pool_classes_by_scheme = {
'http': SOCKSHTTPConnectionPool,
'https': SOCKSHTTPSConnectionPool,
}

def __init__(self, proxy_url, username=None, password=None,
num_pools=10, headers=None, **connection_pool_kw):
parsed = parse_url(proxy_url)

if parsed.scheme == 'socks5':
socks_version = socks.PROXY_TYPE_SOCKS5
rdns = False
elif parsed.scheme == 'socks5h':
socks_version = socks.PROXY_TYPE_SOCKS5
rdns = True
elif parsed.scheme == 'socks4':
socks_version = socks.PROXY_TYPE_SOCKS4
rdns = False

可以看到 requests 和 urllib3 设置的默认连接池大小都是 10.

decorator

基础知识

装饰器是可调用的对象,其参数是另一个函数(被装饰的函数).

手写装饰器

缺点:

  • 不支持关键字参数
  • 遮盖了被装饰函数的 __name__ 和 __doc__ 属性
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
import time


def clock(func):
def wrapper(*args):
t0 = time.perf_counter()
result = func(*args)
elapsed = time.perf_counter() - t0
name = func.__name__
arg_str = ','.join(repr(arg) for arg in args)
print(f'[%0.8fs] {name}({arg_str}) -> {result}' % (elapsed))
return result

return wrapper


@clock
def snooze(seconds):
time.sleep(seconds)


@clock
def factorial(n):
return 1 if n < 2 else n * factorial(n - 1)


if __name__ == '__main__':
print('*' * 40, 'Calling snooze(.123')
snooze(.123)
print('*' * 40, 'Calling factorial(6)')
factorial(6)

输出

1
2
3
4
5
6
7
8
9
**************************************** Calling snooze(.123
[0.12387697s] snooze(0.123) -> None
**************************************** Calling factorial(6)
[0.00000217s] factorial(1) -> 1
[0.00005243s] factorial(2) -> 2
[0.00015136s] factorial(3) -> 6
[0.00019293s] factorial(4) -> 24
[0.00023032s] factorial(5) -> 120
[0.00027032s] factorial(6) -> 720

标准库里的装饰器

functools.wraps 是标准库里的装饰器,它的作用是协助构建行为良好的装饰器,解决了手写装饰器的缺点.

1
2
3
4
5
6
7
8
from functools import wraps

>>> def my_decorator(f):
... @wraps(f)
... def wrapper(*args, **kwds):
... print('Calling decorated function')
... return f(*args, **kwds)
... return wrapper

可以调用类里面方法的装饰器

只要在参数里面加上 self, 就可以使用类里面的方法了.

1
2
3
4
5
6
7
8
from functools import wraps

>>> def my_decorator(f):
... @wraps(f)
... def wrapper(self, *args, **kwds):
... print('Calling decorated function')
... return f(*args, **kwds)
... return wrapper