Dust8 的博客

读书百遍其义自见

0%

最近碰过一个需求,要把 sqlserver 里面的几个字段拼接并做 md5 处理,在插入为一个 guid 字段。由于数据库安装的是中文,用数据库的 HASHBYTESmd5 碰到中文会和正常的不一致,因为它是用 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('我'))

注

备注:

termuxandroid 下的 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 就用它了。puppeteernodejs 的,它的 python
绑定比较好的是 pyppeteer

基本用法

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

启动参数

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

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

请求钩子

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

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
requests2.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]

这种安装方法来自 setuptoolsextras_require . 可以在 requestssetup.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

可以看到 requestsurllib3 设置的默认连接池大小都是 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