forthxu 发布的文章

DNS学习

https://github.com/forthxu/mydns

mydns

DNS查询器,主要用来学习dns协议和c#软件开发。

每当我们在浏览器上敲入任何一个域名访问某个网站的时候,我们都要使用Dns协议进行一次”域名:IP”的查询;作为命令行使用者,与dns有关用的最多的就是Nslookup 命令吧;作为程序员,以c#程序员为例,要得到一个域名的ip大概也是这么一行“System.Net.Dns.GetHostByName(string UriHostName)”。

在这简单使用的背面,很少人会真了解其协议的规则,这也许就是高度封装给程序员带来的一点麻烦吧。下面来了解一下dns协议的内容。

DNS结构:

整个dns分为5个部分,分别为Header、Question、Answer、Authority、Additional。

dns协议头

其中头部的大小是固定的为12字节。这5个部分不是全部都是必须的,在向服务器发送查询请求的时候,只需要前2个。回复的时候也不一定包含5个(按查询的内容和返回的信息而定)。

Header 部分:

header头部分是必须的,无论发送查询或者返回结果都需要该部分,且长度一定,为12字节。结果如下图

dns协议头部

ID:长度为16位,是一个用户发送查询的时候定义的随机数,当服务器返回结果的时候,返回包的ID与用户发送的一致。

QR:长度1位,值0是请求,1是应答。

Opcode:长度4位,值0是标准查询,1是反向查询,2是服务器状态查询。

AA:长度1位,授权应答(Authoritative Answer) - 这个比特位在应答的时候才有意义,指出给出应答的服务器是查询域名的授权解析服务器。

TC:长度1位,截断(TrunCation) - 用来指出报文比允许的长度还要长,导致被截断。

RD:长度1位,期望递归(Recursion Desired) - 这个比特位被请求设置,应答的时候使用的相同的值返回。如果设置了RD,就建议域名服务器进行递归解析,递归查询的支持是可选的。

RA:长度1位,支持递归(Recursion Available) - 这个比特位在应答中设置或取消,用来代表服务器是否支持递归查询。

Z:长度3位,保留值,值为0.

RCode:长度4位,应答码,类似http的stateCode一样,值0没有错误、1格式错误、2服务器错误、3名字错误、4服务器不支持、5拒绝。

QDCount:长度16位,报文请求段中的问题记录数。

ANCount:长度16位,报文回答段中的回答记录数。

NSCOUNT :长度16位,报文授权段中的授权记录数。

ARCOUNT :长度16位,报文附加段中的附加记录数。

Question 部分:

这部分的内容是你要查询的内容。也是必须的。

dns协议question部分

QName:是你要查询的域名,属于不定长字段。他的格式是可变长度数据格式,一般为“长度(1字节)+N字节内容(N由前面的长度定义,不超过63,下面可变数据格式有说明)+~~~+长度0。以一个长度单位N为开始,然后连续的N字节为其内容,然后又是一个N2长度的一字节,然后后面又是N2个字节内容,直到遇到长度为0的长度标记。

假设QName字段的内容是 forth.xu ,则字节内容为:

05 66 6f 72 74 68 02 78 75 0

第一个字节是长度:5,那么接下来的5个字节都是内容66 6f 72 74 68 ,ascii码转过来是“forth”。然后又是长度2,后面2个字节的内容78 75 字母为xu,然后是长度0,表示结束了。最后还要把两段文字组合起来中间加点号成forth.xu。

QType:长度16位,表示查询类型。取值大概如下:

enum QueryType //查询的资源记录类型。

{

A=0x01, //指定计算机 IP 地址。

NS=0x02, //指定用于命名区域的 DNS 名称服务器。

MD=0x03, //指定邮件接收站(此类型已经过时了,使用MX代替)

MF=0x04, //指定邮件中转站(此类型已经过时了,使用MX代替)

CNAME=0x05, //指定用于别名的规范名称。

SOA=0x06, //指定用于 DNS 区域的“起始授权机构”。

MB=0x07, //指定邮箱域名。

MG=0x08, //指定邮件组成员。

MR=0x09, //指定邮件重命名域名。

NULL=0x0A, //指定空的资源记。

WKS=0x0B, //描述已知服务。

PTR=0x0C, //如果查询是 IP 地址,则指定计算机名;否则指定指向其它信息的指针。

HINFO=0x0D, //指定计算机 CPU 以及操作系统类型。

MINFO=0x0E, //指定邮箱或邮件列表信息。

MX=0x0F, //指定邮件交换器。

TXT=0x10, //指定文本信息。

UINFO=0x64, //指定用户信息。

UID=0x65, //指定用户标识符。

GID=0x66, //指定组名的组标识符。

ANY=0xFF //指定所有数据类型。

};

QClass:长度为16位,表示分类。

enum QueryClass //指定信息的协议组。

{

IN=0x01, //指定 Internet 类别。

CSNET=0x02, //指定 CSNET 类别。(已过时)

CHAOS=0x03, //指定 Chaos 类别。

HESIOD=0x04,//指定 MIT Athena Hesiod 类别。

ANY=0xFF //指定任何以前列出的通配符。

};

Answer、Authority、Additional:

接下来的3个结构,格式可以说相同。都是如下图的结构和字段。

dns协议资源结构

Name:回复查询的域名,不定长。 这里的名字和Question结构的名字是一样的,但是为了节省资源,在question结构是这样,在之后的结构中,如果name字段的内容前面有出现了,那么他就不会再浪费空间去重复记录,而是指向某个前面出现了name的位置。如:

在question结构中的name字段的内容为forth.xu,即“05 66 6f 72 74 68 02 78 75 0”。然后在第3个结构中的answer中,第一个字段name的内容也是forth.xu,那么他会指向question中的name地址,让我们去那个地址读name内容。所以此时answer结构的name字段的内容为:

C0 0C

C0:这时不是表示接下来的内容有多长,而是接下来的内容在偏移量中,

0C:十进制是12的意思,就是偏移12个字节。从头开始12位,因为Header结构是固定的12字节,所以偏移0C就是到了Question的Name字段,即上面的“05 66 6f 72 74 68 02 78 75 0”。

Type:同上QType。

Class:同上QClass。

TTL:生存时间。4字节,指示RDATA中的资源记录在缓存的生存时间。

RDLength:资源的长度。

RDdata:资源的内容。

可变长度数据格式 说明:

可变长度有两种内容格式:

长度方式:

1字节长度N + N字节内容 [+ 1字节长度N + N字节内容] + 0x00

N最多不超过63也就是2^6,因为最前面两位用来表示地址方式的偏移量。

地址方式:

第一字节大于等于0xc0开头,表示指针偏移量,所以偏移量的计算其实并不是指c0后面跟着的一个字节,不然一个字节的偏移量最多只有256个位置。

比如偏移量超过255,是300 ,他的小端格式为0x012c,那么他在内存中的表示应该是这样的:0xc12c。

这个300偏移量应该是这样计算的:

300 = 0x012c(小头) = 00000001 00101100(小头) = 00101100 00000001(大头) = 0x2c01(大头)

0xc0 & 0x2c01(大头) = 11000000 & 00101100 00000001 = 00101100 11000001(大头) = 11000001 00101100(小头) = 0xC12C

也就是:

0xc0 & (((0x012c << 8) & 0xff) & (0x012c >> 8)) = 0xC12C

同理反向计算:

((0xC12C & ~0xc0) & 0xff) << 8 & (0xC12C & ~0xc0) >> 8 = 0x012c = 300

需要注意是可变长度的格式有3中结尾方式:

  1. 长度+内容+~+长度0
  2. 偏移标识+偏移量
  3. 长度+内容+~+偏移标识+偏移量

现在来说说这个程序了

我按dns协议的结构把项目分成 MyDnsHeader.cs、MyDnsQuestion.cs、MyDnsRecord.cs 这样的3个大结构。

发送dns请求时只需要构造MyDnsHeader和MyDnsQuestion结构,然后通过GetBytes()函数得到构造好的字节数组,然后通过udp发送出去。然后接受来自服务器的响应,将接收到的字节数组通过Parse(byte[] recvData)方法让3个结构去解析,最后通过这些结构的属性字段获取相应的查询信息。

其中的资源记录,目前能分析 A记录、SOA记录、TXT记录、CNAME记录、MX记录、NS记录。

界面截图:

dns界面截图:

设计和实施 DNS 服务器和客户端服务时可能用到的RFC相关规范
RFC 标题
RFC1034 域名 - 概念和工具
RFC1035 域名 - 实现和规范
RFC1123 Internet 主机 - 应用和支持的要求
RFC1886 支持 IP 版本 6 的 DNS 扩展名
RFC1995 DNS 中的增量区域传输
RFC1996 提示通知区域更改的机制 (DNS NOTIFY)
RFC2136 域名系统中的动态更新 (DNS UPDATE)
RFC2181 对 DNS 规范的说明
RFC2308 DNS 查询的负缓存 (DNS NCACHE)
RFC2535 域名系统安全扩展 (DNSSEC)
RFC2671 DNS 的扩展机制 (EDNS0)
RFC2782 指定服务位置的 DNS RR (DNS SRV)
RFC2930 DNS 的密钥建立 (TKEY RR)
RFC3645 DNS (GSS-TSIG) 密钥事务身分验证的通用安全服务算法
RFC3646 IPv6 (DHCPv6) 动态主机配置协议的 DNS 配置选项

MySQL数据库名、表名、列名、别名、字段值大小写规则

MySQL在Window下数据库名、表名、列名、别名大小写规则不敏感。

MySQL在Linux下数据库名、表名、列名、别名大小写规则是这样的:
  1、数据库名与表名是严格区分大小写的;
  2、表的别名是严格区分大小写的;
  3、列名与列的别名在所有的情况下均是忽略大小写的;
  4、字段值默认字符集情况下是大小写不敏感的。

同时MySQL中数据库名和表名的大小写敏感受参数lower_case_table_names影响,为0时不做处理敏感的地方区分大小写,为1时,表示将转化为小写后存储,查询会做转化,因此不区分大小写,此配置的操作对Window系统的MySQL同样有效。

Linux之所以会有架构敏感,全因为Linux的文件存储系统。数据库名和表名在系统的存储形式分别是文件夹和文件,因此会敏感。

字段值的大小写由mysql的校对规则来控制。提到校对规则,就不得不说字符集。字符集是一套符号和编码,校对规则是在字符集内用于比较字符的一套规则。
一般而言,校对规则以其相关的字符集名开始,通常包括一个语言名,并且以_ci(大小写不敏感)、_cs(大小写敏感)或_bin(二元)结束 。比如 utf8字符集,utf8_general_ci,表示不区分大小写,这个是utf8字符集默认的校对规则;utf8_general_cs表示区分大小写,utf8_bin表示二进制比较,同样也区分大小写 。

关于TCP可靠性的一点思考,借此浅谈应用层协议设计

本文主要讨论如何设计一个可靠的RPC协议。TCP是可靠的传输协议,不会丢包,不会乱序,这是课本上讲述了无数遍的道理。基于TCP的传输理论上来说都是可靠的,但是实际这也得看场景。当我做网络游戏的时候也是一直把它当一个可靠的传输协议来用,从没考虑过TCP丢包的问题。直到当我面临像网络存储、机器学习这样领域时,我发现TCP变得“不可靠”了。

具体来说:

发送方能不能知道已发送的数据对方是不是都收到了?或者,收到了多少?答:不能
如果怀疑对方没收到,有没有办法可以确认对方没有收到? 答:不能
我想发送的是“123”,对方收到的会不会是“1223”? 答:是的,会这样,而且无法避免。
​第一个问题看起来很傻,众所周知TCP有ACK啊,ACK就是用来对方通知接收到了多少个字节的。可是,实际情况是,ACK是操作系统的事儿,它收到ACK后并不会通知用户态的程序。发送的流程是这样的:

应用程序把待发送的数据交给操作系统
操作系统把数据接收到自己的buffer里,接收完成后通知应用程序发送完成
操作系统进行实际的发送操作
操作系统收到对方的ACK
问题来了,假如在执行完第二步之后,网络出现了暂时性故障,TCP连接断了,你该怎么办?如果是网络游戏,这很简单,把用户踢下线,让他重新登录去,活该他网不好。但是如果比较严肃的场合,你当然希望能支持TCP重连。那么问题就来了,应用程序并不知道哪些数据发丢了。

以Windows I/O completion ports举个例子。一般的网络库实现是这样的:在调用WSASend之前,malloc一个WSABuffer,把待发送数据填进去。等到收到操作系统的发送成功的通知后,把buffer释放掉(或者转给下一个Send用)。在这样的设计下,就意味着一旦遇上网络故障,丢失的数据就再也找不回来了。你可以reconnect,但是你没办法resend,因为buffer已经被释放掉了。所以这种管理buffer的方式是一个很失败的设计,释放buffer应当是在收到response之后。

Solution:不要依赖于操作系统的发送成功通知,也不要依赖于TCP的ACK,如果你希望保证对方能收到,那就在应用层设计一个答复消息。再或者说,one-way RPC都是不可靠的,无论传输层是TCP还是UDP,都有可能会丢。​

第二个问题,是设计应用层协议的人很需要考虑的,简单来说,“成功一定是成功但失败不一定是失败”。我想举个例子。假如你现在正在通过网银给房东转账交房租,然后网银客户端说:“网络超时,转账操作可能失败”。你敢重新再转一次吗?我打赌你不敢。

再举个例子,假设你设计了一个分布式文件存储服务。这个服务只有一条“Append”协议:

客户端向服务器发送文件名和二进制data。
服务器把文件打开(不存在则创建),写入数据,然后返回“OK”。中途遇到任何错误则返回“FAIL”
假设你现在有一个20TB的文件,你按照1 GB、1 GB的方式往上传。每发送1 GB,收到OK后,继续发送下1 GB。然后不幸的是中途遇到一个FAIL,你该怎么办?能断点续传吗?NO。因为服务器有可能在写入成功的情况下也返回FAIL(或者网络超时,没有任何回复)。所以你不能重发送未完成的请求。如果你选择从头传,而文件又特别大,那么你可能永远都不会成功。

Solution:采用positioned write。即在客户端发给服务器的请求里加上文件偏移量(offset)。缺点是:若你想要多个客户端同时追加写入同一个文件,那几乎是不可能的。​

​第三个问题:我想发送的是“123”,对方收到的会不会是“1223”?你想要支持重连、重试,那么你得容忍这种情况发生。

Solution:在应用层给每个message标记一个id,让接收者去重即可

接下来讨论下如何关闭连接。简单来说:谁是收到最后一条消息的人,谁来主动关闭tcp 连接。另一方在recv返回0字节之后close,千万不要主动的close。

在协议设计上,分两种情况:

协议是一问一答(类似于HTTP),且发“问”(request)的总是同一方。一方只问,另一方只答
有显式的EOF消息通知对方shutdown。
如果不满足以上两点的任何一点,那么就没有任何一方能判断它收到的消息是不是最后一条,那协议设计有问题,要改!

(p.s. Windows上还有一种方法,就是用半关连接shutdown(SD_SEND)来标志结束,但是操作起来比较复杂,还不如改协议来的快,容易debug)

原文:http://weibo.com/ttarticle/p/show?id=2309404060342857671095
扩展:http://www.ideawu.net/blog/archives/782.html

DNS预获取 dns-prefetch 提升页面载入速度

DNS Prefetch,即DNS预获取,是前端优化的一部分。一般来说,在前端优化中与 DNS 有关的有两点: 一个是减少DNS的请求次数,另一个就是进行DNS预获取

DNS 作为互联网的基础协议,其解析的速度似乎很容易被网站优化人员忽视。现在大多数新浏览器已经针对DNS解析进行了优化,典型的一次DNS解析需要耗费 20-120 毫秒,减少DNS解析时间和次数是个很好的优化方式。DNS Prefetching 是让具有此属性的域名不需要用户点击链接就在后台解析,而域名解析和内容载入是串行的网络操作,所以这个方式能 减少用户的等待时间,提升用户体验

默认情况下浏览器会对页面中和当前域名(正在浏览网页的域名)不在同一个域的域名进行预获取,并且缓存结果,这就是隐式的 DNS Prefetch。如果想对页面中没有出现的域进行预获取,那么就要使用显示的 DNS Prefetch 了。

目前大多数浏览器已经支持此属性,支持版本如下:

列表项目

  • Safari: 5+
  • Chrome: All
  • Firefox: 3.5+
  • Opera: Unknown
  • IE: 9+ (called “Pre-resolution” on blogs.msdn.com)

其中 Chrome 和 Firefox 3.5+ 内置了 DNS Prefetching 技术并对DNS预解析做了相应优化设置。所以 即使不设置此属性,Chrome 和 Firefox 3.5+ 也能自动在后台进行预解析 。

目前很多大型站点也应用了这一优化,例如:

淘宝:
taobaodnsmeta.png

支付宝:
zhifubaodnsmeta.png

网易:
wangyidnsmeta.png

DNS Prefetch 应该尽量的放在网页的前面,推荐放在 后面。具体使用方法如下:

<meta http-equiv="x-dns-prefetch-control" content="on">
<link rel="dns-prefetch" href="//www.itechzero.com">
<link rel="dns-prefetch" href="//api.share.baidu.com">
<link rel="dns-prefetch" href="//bdimg.share.baidu.com">

需要注意的是,虽然使用 DNS Prefetch 能够加快页面的解析速度,但是也不能滥用,因为有开发者指出 禁用DNS 预读取能节省每月100亿的DNS查询 。

只需要在用户在第一次打开网站时使用DNS Prefetch,没有必要每个页面都使用DNS Prefetch,否则就是重复DNS读取了,反而还无形中增加了DNS查询的次数,效果适得其反。(此处有疑)

如果需要禁止隐式的 DNS Prefetch,可以使用以下的标签:

<meta http-equiv="x-dns-prefetch-control" content="off">

文章主要来源前端优化一(已打不开)

参考:
事半功倍:你应该知道的HTML5五大特性
减少域名DNS解析时间将网页加载速度提升新层次-DNS缓存/预读取/拆分域名
浏览器的加载过程

Python2字符编码问题小结

Python docs - Unicode HOWTO

Python docs - Built-in Types

Stack Overflow - Why does Python print unicode characters when the default encoding is ASCII?

理论

编码中的Unicode和UTF-8

Unicode是字符集,UTF-8Unicode的一种编码方式,并列的还包括UTF-16UTF-32等。

某个字符的Unicode通过查询标准得到,其UTF-8编码由Unicode码计算得到。

Python2中的str和unicode

strunicode是两个不同的类。

str存储的是已经编码后的字节序列,输出时看到每个字节用16进制表示,以\x开头。每个汉字会占用3个字节的长度。

>>> a = '啊哈哈'
>>> type(a)
<type 'str'>
>>> a
'\xe5\x95\x8a\xe5\x93\x88\xe5\x93\x88'
>>> len(a)
9
>>> a[2]
'\x8a'

unicode是“字符”串,存储的是编码前的字符,输出是看到字符以\u开头。每个汉字占用一个长度。定义一个Unicode对象时,以u
开头。

>>> b = u'哟呵呵'
>>> type(b)
<type 'unicode'>
>>> b
u'\u54df\u5475\u5475'
>>> len(b)
3
>>> b[2]
u'\u5475'

str可以通过decode()方法转化为unicode对象,参数指明编码方式。

>>> a.decode('utf-8')
u'\u554a\u54c8\u54c8'

unicode可以通过encode()方法转化为str对象,参数指明编码方式。

>>> b.encode('utf-8')
'\xe5\x93\x9f\xe5\x91\xb5\xe5\x91\xb5'

默认编码

Python2中的默认编码,有多个不同的变量。

  1. 代码文件开头的coding

     # -*- coding: utf-8 -*-
    

     # coding=utf-8
    

    指明代码文件中的字符编码,用于代码文件中出现中文的情况。

     % cat hello.py
     #! /usr/bin/env python
     # coding=utf-8
     print '泥壕'
     
     % python hello.py
     泥壕
    

    如果不设置,默认是ascii,当出现中文字符时就不能正常识别。

     % cat hello.py
     #! /usr/bin/env python
     print '泥壕'
     
     % python hello.py
         File "hello.py", line 2
     SyntaxError: Non-ASCII character '\xe6' in file hello.py on line 2, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details
    
  1. sys.stdin.encodingsys.stdout.encoding

    sdtinstdout输入输出使用的编码,包命令行参数和print输出,由locale环境变量决定。

    en_US.UTF-8的系统中,默认值是UTF-8

  2. sys.getdefaultencoding()

    文件读写和字符串处理等操作使用的默认编码。

    默认值是ascii

字符串拼接

unicodestr类型通过+拼接时,输出结果是unicode类型,相当于先将str类型的字符串通过decode()方法解码成unicode,再拼接。此时如果解码时没有明确指明编码类型,可能会出现错误。

>>> a = '啊哈哈'
>>> b = u'哟呵呵'
>>>
>>> a + b
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)
>>>
>>> a.decode('utf-8') + b
u'\u554a\u54c8\u54c8\u54df\u5475\u5475'

错误提到'ascii' codec can't decode byte 0xe5,这是因为自动将str类型的变量按照默认的编码格式sys.getdefaultencoding()来解码,默认编码即ascii,而这个字符不在ascii的范围内,就出现了错误。

>>> import sys
>>> reload(sys)
<module 'sys' (built-in)>
>>> sys.setdefaultencoding('utf-8')
>>>
>>> a = '啊哈哈'
>>> b = u'哟呵呵'
>>> a + b
u'\u554a\u54c8\u54c8\u54df\u5475\u5475'

文件读取和json解析

读文件得到的结果是str类型,以\x开头的十六进制表示。

>>> f = open('t.txt')
>>> a = f.read()
>>> a
'{"hello":"\xe5\x92\xa9"}\n'

而经过json解析后会自动转为unicode

>>> json.loads(a)
{u'hello': u'\u54a9'}

输出

输出到文件

str类型可以输出到文件,而unicode类型必须先编码成str

>>> a = '啊哈哈'
>>> b = u'哟呵呵'
>>> a
'\xe5\x95\x8a\xe5\x93\x88\xe5\x93\x88'
>>> b
u'\u54df\u5475\u5475'
>>> 
>>> f = open('t.txt', 'w')
>>> f.write(a)
>>> f.write(b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-2: ordinal not in range(128)
>>> f.write(b.encode('utf-8'))

unicode输出到文件时的错误是由于默认编码为ascii,无法自动完成编码过程。如果将sys.getdefaultencoding()编码设置成了utf-8就可以自动完成转换过程了。

>>> import sys
>>> reload(sys)
<module 'sys' (built-in)>
>>> sys.setdefaultencoding('utf-8')
>>>
>>> f.write(b)

计算md5

同样,md5计算也要求输入的unicode先编码。

>>> a = '啊哈哈'
>>> b = u'哟呵呵'
>>> import hashlib
>>> hashlib.md5(a).hexdigest()
'f38b302e2993ec3fdad79c4d76074b21'
>>> hashlib.md5(b).hexdigest()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-2: ordinal not in range(128)
>>> hashlib.md5(b.encode('utf-8')).hexdigest()
'c02dc06719bafeaf60505b11d3c0c90a'

输出到stdout

输出到stdout时,默认编码是sys.stdout.encoding,默认值取决于系统环境变量,所以print输出汉字时才可以不用指定utf-8

>>> import sys
>>> sys.stdout.encoding
'UTF-8'
>>> print u'\u54a9'
咩

而在zh_CN.GB2312的环境中,默认值不是utf-8,就不能正常输出了。

>>> import sys
>>> sys.stdout.encoding
'ANSI_X3.4-1968'
>>> print u'\u54a9'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\u54a9' in position 0: ordinal not in range(128)

命令行参数读取

通过sys.argvargparse得到的命令行参数都是编码后的str类型,以\x开头的十六进制表示。可以通过sys.stdin.encoding得到命令行传入的编码类型,解码成unicode

#! /usr/bin/env python
# coding = utf-8
import sys

print repr(sys.argv[1])
print sys.stdin.encoding
print repr(sys.argv[1].decode(sys.stdin.encoding))

输出结果。

~/workspace % python hello.py "哇嘿嘿"  
'\xe5\x93\x87\xe5\x98\xbf\xe5\x98\xbf'
UTF-8
u'\u54c7\u563f\u563f'

如果命令行环境已经改成GB2312等其他编码,python找不到与之匹配的编码类型,就会将默认编码sys.stdin.encoding设置成ascii,无法通过这种方法正常解码成unicode

带\u的字符串转unicode

可能会遇到汉字被转换成unicode编码的形式表示的情况,即一个汉字被表示成了\u????的形式。

>>> a = u'咩'
>>> a
u'\u54a9'
>>> b = '\u54a9'
>>> b
'\\u54a9'

上述b就是这样的情况。此时b是一个长度为6的字符串,而不是一个汉字。

要把b表示为汉字编码有两种方法。

  1. unicode-escape编码。

     >>> unicode(b, 'unicode-escape')
     u'\u54a9'
    

     >>> b.decode('unicode-escape')
     u'\u54a9'
    
  1. eval拼接。

     >>> eval('u"' + b.replace('"', r'\"')+'"')
     u'\u54a9'
    

web浏览器跨域

什么是跨域

http://bigdots.github.io/2015/12/23/实现跨域/

跨域请求了什么内容会被阻止

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS

谁请求了跨域,谁阻止了跨域

https://lengzzz.com/note/cross-origin-http-request

有什么方法跨域

http://tech.jandou.com/cross-domain.html

利用Access-Control-Allow-Origin响应头解决跨域请求原理

COR的实现标准就是CORS协议。

对于浏览器来说,COR请求都是Javascript发起的,COR请求有两种:

1、简单的COR请求,它可以直接向外域资源发起请求。它必须仅仅包含简单的方法和头,具体定义看https://www.w3.org/TR/cors/#resource-requests。

2、如果COR包含复杂的方法和头,它需要发出预检验(Preflight)请求,它先向资源服务器发出一个OPTIONS方法、包含“Origin”头的请求。该回复可以控制COR请求的方法,HTTP头以及验证等信息。只有该请求获得允许以后,才会发起真实的外域请求。
https://blog.csdn.net/hehexiaoxia/article/details/61916737
https://blog.csdn.net/enter89/article/details/51205752

网页正文提取算法和相似文章比较算法

两个比较好的网页正文提取算法:

国内
哈工大的《基于行块分布函数的通用网页正文抽取》该算法开源网址为http://code.google.com/p/cx-extractor/,文章中呈准确率95%以上,对1000个网页抽取耗时21.29秒。看了文章感觉不错,无需html解析,效率应该会高些。

Html2Article C# http://www.cnblogs.com/jasondan/p/3497757.html

国外
大名鼎鼎的arc90实验室的Readability,该算法已经商业化实现了firefox,chrome插件,及flipboard,并且已经集成进了safari浏览器。未详细测试,大致测试感觉准确率应该至少在90%以上。该算法需要解析DOM树,因此稍执行效率稍微慢一些。大致过程为,先解析DOM树,所有标签小写。然后去除所有“script”标签内容,再通过一对正则表达式的配合提取。具体算法还未看。其插件中包含算法JAVASCRIPT源码。
有热心人士已将其用c#和php实现,源码地址如下:

官方网站 http://www.readability.com/
c#实现一:https://github.com/marek-stoj/NReadability
c#实现二:https://github.com/marek-stoj/NReadability
php实现一: https://bitbucket.org/fivefilters/php-readability
php实现二: https://github.com/feelinglucky/php-readability 作者主页: http://www.gracecode.com/archives/3061/
node.js版:https://github.com/arrix/node-readability/

原文:http://www.cnblogs.com/phoenixnudt/articles/2382140.html

相似文章算法

『simhash算法』

simhash是google用来处理海量文本去重的算法。 google出品,你懂的。 simhash最牛逼的一点就是将一个文档,最后转换成一个64位的字节,暂且称之为特征字,然后判断重复只需要判断他们的特征字的距离是不是<n(根据经验这个n一般取值为3),就可以判断两个文档是否相似。

simhash 实现的工程项目

C++ 版本 simhash
Golang 版本 gosimhash

『百度的去重算法』

百度的去重算法最简单,就是直接找出此文章的最长的n句话,做一遍hash签名。n一般取3。 工程实现巨简单,据说准确率和召回率都能到达80%以上。

『shingle算法』

shingle原理略复杂,不细说。 shingle算法我认为过于学院派,对于工程实现不够友好,速度太慢,基本上无法处理海量数据。

『其他算法』
具体看微博上的讨论

原文:https://yanyiwu.com/work/2014/01/30/simhash-shi-xian-xiang-jie.html

libc.so.6误删后的修复方法

因为测试Selenium的chromedriver需要2.15以上的libc.so,因此自己编译安装,删除再做软链时发现ls、dir一系列命名不能使用的情况,提示:

rm: error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory

才意识到动到了系统核心的动态库,补救方法:

LD_PRELOAD=/lib64/libc-2.12.so ln -sf /lib64/libc-2.12.so /lib64/libc.so.6

原理就是优先查找指定动态库。

扩展阅读:
libstdc++.so.6升级

https://github.com/FezVrasta/ark-server-tools/wiki/Install-of-required-versions-of-glibc-and-gcc-on-RHEL-CentOS

https://centos.pkgs.org/5/centos-x86_64/libstdc++-4.1.2-55.el5.x86_64.rpm.html

ftp://ftp.gwdg.de/pub/misc/gcc/releases/gcc-4.9.4/

http://www.mudbest.com/centos%E5%8D%87%E7%BA%A7gcc4-4-7%E5%8D%87%E7%BA%A7gcc4-8%E6%89%8B%E8%AE%B0/

验证码识别

对验证码识别大致分这几个过程,

  • 第一步获取验证码,
  • 第二对验证码处理,如果颜色单一没什么背景杂色就直接二值化处理,注意阙值,有干扰线的把干扰线和背景去掉,最终变为背景为白色,验证码前景色为黑色。
  • 第三步就是切割,把验证码从图片中切割出来,
  • 第四建立识别库,切割后的图片分类存入识别库,让后需要让程序学习一些验证码后,识别库就有了样例。第四步就是那当前是别的验证码和识别库的验证码进行比对,达到识别验证码的结果。

实例程序代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Net;
using System.Collections;
using System.IO;

namespace CheckCodeRecognizeLib
{
    public class CheckCodeRecognize
    {
        #region 成员变量
        //色差阀值 越小消除的杂色越多
        private double threshold = 150;
        //二值阀值 越大效果越不明显
        private double ezFZ = 0.6;
        //背景近似度阀值
        private double bjfz = 80;
        //图片路径
        private string imgPath = string.Empty;
        //每个字符最小宽度
        public int MinWidthPerChar = 7;
        //每个字符最大宽度
        public int MaxWidthPerChar = 18;
        //每个字符最小高度
        public int MinHeightPerChar = 10;
        //学习库保存的路径
        private readonly string samplePath = AppDomain.CurrentDomain.BaseDirectory + "Sample\\";
        #endregion

        #region 图片处理
        /// <summary>
        /// 对传入的图片二值化
        /// </summary>
        /// <param name="bitmap">传入的原图片</param>
        /// <returns>处理过后的图片</returns>
        private Bitmap EZH(Bitmap bitmap)
        {
            if (bitmap != null)
            {
                var img = new Bitmap(bitmap);
                for (var x = 0; x < img.Width; x++)
                {
                    for (var y = 0; y < img.Height; y++)
                    {
                        Color color = img.GetPixel(x, y);
                        if (color.GetBrightness() < ezFZ)
                        {
                            img.SetPixel(x, y, Color.Black);
                        }
                        else
                        {
                            img.SetPixel(x, y, Color.White);
                        }
                    }
                }
                return img;                
            }
            return null;
            
        }
        /// <summary>
        /// 去背景
        /// 把图片中最多的一部分颜色视为背景色 选出来后替换为白色
        /// </summary>
        /// <param name="bitmapImg">将要处理的图片</param>
        /// <returns>返回去过背景的图片</returns>
        private Bitmap RemoveBackGround(Bitmap bitmapImg)
        {
            if (bitmapImg == null)
            {
                return null;
            }          
            //key 颜色  value颜色对应的数量
            Dictionary<Color, int> colorDic = new Dictionary<Color, int>();
            //获取图片中每个颜色的数量
            for (var x = 0; x < bitmapImg.Width; x++)
            {
                for (var y = 0; y < bitmapImg.Height; y++)
                {
                    //删除边框
                    if (y == 0 || y == bitmapImg.Height)
                    {
                        bitmapImg.SetPixel(x, y, Color.White);
                    }

                    var color = bitmapImg.GetPixel(x, y);
                    var colorRGB = color.ToArgb();

                    if (colorDic.ContainsKey(color))
                    {
                        colorDic[color] = colorDic[color] + 1;
                    }
                    else
                    {
                        colorDic[color] = 1;
                    }
                }
            }
            //图片中最多的颜色
            Color maxColor = colorDic.OrderByDescending(o => o.Value).FirstOrDefault().Key;
            //图片中最少的颜色
            Color minColor = colorDic.OrderBy(o => o.Value).FirstOrDefault().Key;

            Dictionary<int[], double> maxColorDifDic = new Dictionary<int[], double>();
            //查找 maxColor 最接近颜色
            for (var x = 0; x < bitmapImg.Width; x++)
            {
                for (var y = 0; y < bitmapImg.Height; y++)
                {
                    maxColorDifDic.Add(new int[] { x, y }, GetColorDif(bitmapImg.GetPixel(x, y), maxColor));
                }
            }
            //去掉和maxColor接近的颜色 即 替换成白色
            var maxColorDifList = maxColorDifDic.OrderBy(o => o.Value).Where(o => o.Value < bjfz).ToArray();
            foreach (var kv in maxColorDifList)
            {
                bitmapImg.SetPixel(kv.Key[0], kv.Key[1], Color.White);
            }
            return bitmapImg;
           
        }
        /// <summary>
        /// 获取色差
        /// </summary>
        /// <param name="color1"></param>
        /// <param name="color2"></param>
        /// <returns></returns>
        private double GetColorDif(Color color1, Color color2)
        {
            return Math.Sqrt((Math.Pow((color1.R - color2.R), 2) +
                Math.Pow((color1.G - color2.G), 2) +
                Math.Pow((color1.B - color2.B), 2)));
        }
        /// <summary>
        /// 去掉目标干扰线
        /// </summary>
        /// <param name="img">将要处理的图片</param>
        /// <returns>去掉干干扰线处理过的图片</returns>  
        private Bitmap btnDropDisturb_Click(Bitmap img)
        {
            if (img == null)
            {
                return null;
            }         
            byte[] p = new byte[9]; //最小处理窗口3*3
            //去干扰线
            for (var x = 0; x < img.Width; x++)
            {
                for (var y = 0; y < img.Height; y++)
                {
                    Color currentColor = img.GetPixel(x, y);
                    int color = currentColor.ToArgb();

                    if (x > 0 && y > 0 && x < img.Width - 1 && y < img.Height - 1)
                    {
                        #region 中值滤波效果不好
                        ////取9个点的值
                        //p[0] = img.GetPixel(x - 1, y - 1).R;
                        //p[1] = img.GetPixel(x, y - 1).R;
                        //p[2] = img.GetPixel(x + 1, y - 1).R;
                        //p[3] = img.GetPixel(x - 1, y).R;
                        //p[4] = img.GetPixel(x, y).R;
                        //p[5] = img.GetPixel(x + 1, y).R;
                        //p[6] = img.GetPixel(x - 1, y + 1).R;
                        //p[7] = img.GetPixel(x, y + 1).R;
                        //p[8] = img.GetPixel(x + 1, y + 1).R;
                        ////计算中值
                        //for (int j = 0; j < 5; j++)
                        //{
                        //    for (int i = j + 1; i < 9; i++)
                        //    {
                        //        if (p[j] > p[i])
                        //        {
                        //            s = p[j];
                        //            p[j] = p[i];
                        //            p[i] = s;
                        //        }
                        //    }
                        //}
                        ////      if (img.GetPixel(x, y).R < dgGrayValue)
                        //img.SetPixel(x, y, Color.FromArgb(p[4], p[4], p[4]));    //给有效值付中值
                        #endregion

                        //上 x y+1
                        double upDif = GetColorDif(currentColor, img.GetPixel(x, y + 1));
                        //下 x y-1
                        double downDif = GetColorDif(currentColor, img.GetPixel(x, y - 1));
                        //左 x-1 y
                        double leftDif = GetColorDif(currentColor, img.GetPixel(x - 1, y));
                        //右 x+1 y
                        double rightDif = GetColorDif(currentColor, img.GetPixel(x + 1, y));
                        //左上
                        double upLeftDif = GetColorDif(currentColor, img.GetPixel(x - 1, y + 1));
                        //右上
                        double upRightDif = GetColorDif(currentColor, img.GetPixel(x + 1, y + 1));
                        //左下
                        double downLeftDif = GetColorDif(currentColor, img.GetPixel(x - 1, y - 1));
                        //右下
                        double downRightDif = GetColorDif(currentColor, img.GetPixel(x + 1, y - 1));

                        ////四面色差较大
                        //if (upDif > threshold && downDif > threshold && leftDif > threshold && rightDif > threshold)
                        //{
                        //    img.SetPixel(x, y, Color.White);
                        //}
                        //三面色差较大
                        if ((upDif > threshold && downDif > threshold && leftDif > threshold)
                            || (downDif > threshold && leftDif > threshold && rightDif > threshold)
                            || (upDif > threshold && leftDif > threshold && rightDif > threshold)
                            || (upDif > threshold && downDif > threshold && rightDif > threshold))
                        {
                            img.SetPixel(x, y, Color.White);
                        }

                        List<int[]> xLine = new List<int[]>();
                        //去横向干扰线  原理 如果这个点上下有很多白色像素则认为是干扰
                        for (var x1 = x + 1; x1 < x + 10; x1++)
                        {
                            if (x1 >= img.Width)
                            {
                                break;
                            }

                            if (img.GetPixel(x1, y + 1).ToArgb() == Color.White.ToArgb()
                                && img.GetPixel(x1, y - 1).ToArgb() == Color.White.ToArgb())
                            {
                                xLine.Add(new int[] { x1, y });
                            }
                        }
                        if (xLine.Count() >= 4)
                        {
                            foreach (var xpoint in xLine)
                            {
                                img.SetPixel(xpoint[0], xpoint[1], Color.White);
                            }
                        }

                        //去竖向干扰线

                    }
                }
            }
            return img;
        }
        /// <summary>
        /// 对图片先竖向分割,再横向分割
       /// </summary>
       /// <param name="img">将要分割的图片</param>
        /// <returns>所有分割后的字符图片</returns>
        private Bitmap[] SplitImage(Bitmap img)
        {
            if (img == null)
            {
                return null;
            }
            List<int[]> xCutPointList = GetXCutPointList(img);
            List<int[]> yCutPointList = GetYCutPointList(xCutPointList, img);       
            Bitmap[] bitmapArr = new Bitmap[5];
            //对分割的部分划线
            for (int i = 0; i < xCutPointList.Count(); i++)
            {
                int xStart = xCutPointList[i][0];
                int xEnd = xCutPointList[i][1];
                int yStart = yCutPointList[i][0];
                int yEnd = yCutPointList[i][1];
                if (i >= 4) break;
                bitmapArr[i]= (Bitmap)AcquireRectangleImage(img,
                    new Rectangle(xStart, yStart, xEnd - xStart + 1, yEnd - yStart + 1));
            }
            return bitmapArr;
        }
        /// <summary>
        /// 分别从图片的上下寻找像素点大于阙值的地方,然后获取有黑色像素的有效区域
        /// </summary>
        /// <param name="xCutPointList">x轴范围的x坐标集合</param>
        /// <param name="img">目标图片</param>
        /// <returns>y轴坐标开始和结束点,其实就是黑色像素图片的有效区域</returns>
        private List<int[]> GetYCutPointList(List<int[]> xCutPointList, Bitmap img)
        {
            List<int[]> list = new List<int[]>();
            //获取图像最上面Y值
            int topY = 0;
            //获取图像最下面的Y值
            int bottomY = 0;
            foreach (var xPoint in xCutPointList)
            {
                for (int ty = 1; ty < img.Height; ty++)
                {
                    int xStart = xPoint[0];
                    int xEnd = xPoint[1];
                    int blackCount = GetBlackPXCountInY(ty, 2, xStart, xEnd, img);
                    if (blackCount > 3)
                    {
                        topY = ty;
                        break;
                    }
                }
                for (int by = img.Height; by > 1; by--)
                {
                    int xStart = xPoint[0];
                    int xEnd = xPoint[1];
                    int blackCount = GetBlackPXCountInY(by, -2, xStart, xEnd, img);
                    if (blackCount > 3)
                    {
                        bottomY = by;
                        break;
                    }
                }
                list.Add(new int[] { topY, bottomY });

            }
            return list;
        }
        /// <summary>
        /// 获取分割后某区域的黑色像素
        /// </summary>
        /// <param name="startY"></param>
        /// <param name="offset"></param>
        /// <param name="startX"></param>
        /// <param name="endX"></param>
        /// <param name="img"></param>
        /// <returns></returns>
        private int GetBlackPXCountInY(int startY, int offset, int startX, int endX, Bitmap img)
        {
            int blackPXCount = 0;
            int startY1 = offset > 0 ? startY : startY + offset;
            int offset1 = offset > 0 ? startY + offset : startY;
            for (var x = startX; x <= endX; x++)
            {
                for (var y = startY1; y < offset1; y++)
                {
                    if (y >= img.Height)
                    {
                        continue;
                    }
                    if (img.GetPixel(x, y).ToArgb() == Color.Black.ToArgb())
                    {
                        blackPXCount++;
                    }
                }
            }
            return blackPXCount;
        }
        /// <summary>
        /// 获取一个垂直区域内的黑色像素
        /// </summary>
        /// <param name="startX">开始x</param>
        /// <param name="offset">左偏移像素</param>
        /// <returns></returns>
        private int GetBlackPXCountInX(int startX, int offset, Bitmap img)
        {
            int blackPXCount = 0;
            for (int x = startX; x < startX + offset; x++)
            {
                if (x >= img.Width)
                {
                    continue;
                }
                for (var y = 0; y < img.Height; y++)
                {
                    if (img.GetPixel(x, y).ToArgb() == Color.Black.ToArgb())
                    {
                        blackPXCount++;
                    }
                }
            }
            return blackPXCount;
        }
        /// <summary>
        /// 获取竖向分割点
        /// </summary>
        /// <param name="img"></param>
        /// <returns>List int[xstart xend]</returns>
        private List<int[]> GetXCutPointList(Bitmap img)
        {
            //分割点  List<int[xstart xend]>
            List<int[]> xCutList = new List<int[]>();
            int startX = -1;//-1表示在寻找开始节点
            for (var x = 0; x < img.Width; x++)
            {
                if (startX == -1)//开始点
                {
                    int blackPXCount = GetBlackPXCountInX(x, 2, img);
                    //如果大于有效像素则是开始节点 ,0-x的矩形区域大于3像素,认为是字母,防止一些噪点被切割           
                    if (blackPXCount > 5)
                    {
                        startX = x;

                    }
                }
                else//结束点
                {
                    if (x == img.Width - 1)//判断是否最后一列
                    {
                        xCutList.Add(new int[] { startX, x });
                        break;
                    }
                    else if (x >= startX + MinWidthPerChar)//隔开一定距离才能结束分割
                    {
                        int blackPXCount = GetBlackPXCountInX(x, 2, img);//判断后面区域黑色像素点的个数
                        //小于等于阀值则是结束节点                       
                        if (blackPXCount < 2)
                        {

                            if (x > startX + MaxWidthPerChar)//尽量控制不执行
                            {
                                //大于最大字符的宽度应该是两个字符粘连到一块了 从中间分开
                                int middleX = startX + (x - startX) / 2;
                                xCutList.Add(new int[] { startX, middleX });
                                xCutList.Add(new int[] { middleX + 1, x });
                            }
                            else
                            {
                                //验证黑色像素是否太少
                                blackPXCount = GetBlackPXCountInX(startX, x - startX, img);
                                if (blackPXCount <= 10)
                                {
                                    startX = -1;//重置开始点
                                }
                                else
                                {
                                    xCutList.Add(new int[] { startX, x });
                                }
                            }
                            startX = -1;//重置开始点
                        }
                    }
                }
            }
            return xCutList;
        }
        /// <summary>
        /// 截取图像的矩形区域
        /// </summary>
        /// <param name="source">源图像对应picturebox1</param>
        /// <param name="rect">矩形区域,如上初始化的rect</param>
        /// <returns>矩形区域的图像</returns>
        private Image AcquireRectangleImage(Image source, Rectangle rect)
        {
            if (source == null || rect.IsEmpty) return null;
            //Bitmap bmSmall = new Bitmap(rect.Width, rect.Height, System.Drawing.Imaging.PixelFormat.Format32bppRgb);
            Bitmap bmSmall = new Bitmap(rect.Width, rect.Height, source.PixelFormat);

            using (Graphics grSmall = Graphics.FromImage(bmSmall))
            {
                grSmall.DrawImage(source,
                                  new System.Drawing.Rectangle(0, 0, bmSmall.Width, bmSmall.Height),
                                  rect,
                                  GraphicsUnit.Pixel);
                grSmall.Dispose();
            }
            return bmSmall;
        }
        #endregion

        #region 图片识别
        /// <summary>
        /// 返回两图比较的相似度 最大1
        /// </summary>
        /// <param name="compareImg">对比图</param>
        /// <param name="mainImg">要识别的图</param>
        /// <returns></returns>
        private double CompareImg(Bitmap compareImg, Bitmap mainImg)
        {
            int img1x = compareImg.Width;
            int img1y = compareImg.Height;
            int img2x = mainImg.Width;
            int img2y = mainImg.Height;
            //最小宽度
            double min_x = img1x > img2x ? img2x : img1x;
            //最小高度
            double min_y = img1y > img2y ? img2y : img1y;

            double score = 0;
            //重叠的黑色像素
            for (var x = 0; x < min_x; x++)
            {
                for (var y = 0; y < min_y; y++)
                {
                    if (compareImg.GetPixel(x, y).ToArgb() == Color.Black.ToArgb()
                        && compareImg.GetPixel(x, y).ToArgb() == mainImg.GetPixel(x, y).ToArgb())
                    {
                        score++;
                    }
                }
            }
            double originalBlackCount = 0;
            //对比图片的黑色像素
            for (var x = 0; x < img1x; x++)
            {
                for (var y = 0; y < img1y; y++)
                {
                    if (Color.Black.ToArgb() == compareImg.GetPixel(x, y).ToArgb())
                    {
                        originalBlackCount++;
                    }
                }
            }
            return score / originalBlackCount;
        }
        /// <summary>
        /// 用所有的学习的图片对比当前图,通过黑色和图片比率获取最大相似度的字符图片,从而识别
        /// </summary>
        /// <param name="imgArr">要识别图片的数组</param>
        /// <returns>识别后的字符串</returns>
        public string RecognizeCheckCodeImg(Bitmap bitImg)
        {
            Bitmap EZHimg = EZH(bitImg);
            Bitmap[] imgArr = SplitImage(EZHimg);
            string returnString = string.Empty;
            for (int i = 0; i < imgArr.Length; i++)
            {
                if (imgArr[i] == null)
                {
                    continue;
                }
                var img = imgArr[i];
                if (img == null)
                {
                    continue;
                }
                string[] detailPathList = Directory.GetDirectories(samplePath);
                if (detailPathList == null || detailPathList.Length == 0)
                {
                    continue;
                }
                string resultString = string.Empty;
                //config.txt 文件中指定了识别字母的顺序
                string configPath = samplePath + "config.txt";
                if (!File.Exists(configPath))
                {
                    Console.WriteLine("config.txt文件不存在,无法识别");
                    return null;
                }
                string configString = File.ReadAllText(configPath);
                double maxRate = 0;//相似度  最大1
                foreach (char resultChar in configString)
                {
                    string charPath = samplePath + resultChar.ToString();//特征目录存储路径
                    if (!Directory.Exists(charPath))
                    {
                        continue;
                    }
                    string[] fileNameList = Directory.GetFiles(charPath);
                    if (fileNameList == null || fileNameList.Length == 0)
                    {
                        continue;
                    }


                    foreach (string filename in fileNameList)
                    {
                        Bitmap imgSample = new Bitmap(filename);
                        //过滤宽高相差太大的
                        if (Math.Abs(imgSample.Width - img.Width) >= 2
                            || Math.Abs(imgSample.Height - img.Height) >= 3)
                        {
                            continue;
                        }
                        //当前相似度                       
                        double currentRate = CompareImg(imgSample, img);
                        if (currentRate > maxRate)
                        {
                            maxRate = currentRate;
                            resultString = resultChar.ToString();
                        }
                        imgSample.Dispose();
                    }
                }
                returnString = returnString + resultString;
            }
            return returnString;
        }
        #endregion

    }
}

程序验证:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Drawing;
using System.IO;

namespace CheckCodeRecognizeLibTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("正在下载验证码......");
            //CookieContainer cc = new CookieContainer();
            //byte[] imgByte = HttpWebRequestForBPMS.GetWebResorce("http://hd.cnrds.net/hd/login.do?action=createrandimg",cc);
            //MemoryStream ms1 = new MemoryStream(imgByte);
           // Bitmap bm = (Bitmap)Image.FromStream(ms1); 
            Bitmap img = HttpWebRequestForBPMS.GetWebImage("http://hd.cnrds.net/hd/login.do?action=createrandimg");
            Console.WriteLine("验证码下载成功,正在识别.....");
            CheckCodeRecognizeLib.CheckCodeRecognize regImg = new CheckCodeRecognizeLib.CheckCodeRecognize();
            string regResult= regImg.RecognizeCheckCodeImg(img);
            Console.WriteLine("验证码识别成功,验证码结果为:"+regResult);
            
          
        }       
    }
  
    
}

原文:http://www.cnblogs.com/fuchongjundream/p/5403193.html

采集搜狗公众号文章

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# https://github.com/forthxu/WechatSearchProjects 还包同样功能改用Scrapy采集

import sys
import re
import urllib, urllib2
import requests
import pymongo
import datetime
from bs4 import BeautifulSoup
import multiprocessing as mp


class MongoDBIO:
    # 申明相关的属性
    def __init__(self, host, port, name, password, database, collection):
        self.host = host
        self.port = port
        self.name = name
        self.password = password
        self.database = database
        self.collection = collection

    # 连接数据库,db和posts为数据库和集合的游标
    def Connection(self):
        # connection = pymongo.Connection() # 连接本地数据库
        connection = pymongo.Connection(host=self.host, port=self.port)
        # db = connection.datas
        db = connection[self.database]
        if self.name or self.password:
            db.authenticate(name=self.name, password=self.password) # 验证用户名密码
        # print "Database:", db.name
        # posts = db.cn_live_news
        posts = db[self.collection]
        # print "Collection:", posts.name
        return posts

# # 保存操作
# def ResultSave(save_host, save_port, save_name, save_password, save_database, save_collection, save_contents):
#     posts = MongoDBIO(save_host, save_port, save_name, save_password, save_database, save_collection).Connection()
#
#     for save_content in save_contents:
#         posts.save(save_content)
# 保存操作
def ResultSave(save_host, save_port, save_name, save_password, save_database, save_collection, save_content):
    posts = MongoDBIO(save_host, save_port, save_name, save_password, save_database, save_collection).Connection()
    posts.save(save_content)


def GetTitleUrl(url, data):
    content = requests.get(url=url, params=data).content # GET请求发送
    soup = BeautifulSoup(content)
    tags = soup.findAll("h4")
    titleurl = []
    for tag in tags:
        item = {"title":tag.text.strip(), "link":tag.find("a").get("href"), "content":""}
        titleurl.append(item)
    return titleurl

def GetContent(url):
    soup = BeautifulSoup(requests.get(url=url).content)
    tag = soup.find("div", attrs={"class":"rich_media_content", "id":"js_content"}) # 提取第一个标签
    content_list = [tag_i.text for tag_i in tag.findAll("p")]
    content = "".join(content_list)
    return content

def ContentSave(item):
    # 保存配置
    save_host = "localhost"
    save_port = 27017
    save_name = ""
    save_password = ""
    save_database = "testwechat"
    save_collection = "result"

    save_content = {
        "title":item["title"],
        "link":item["link"],
        "content":item["content"]
    }

    ResultSave(save_host, save_port, save_name, save_password, save_database, save_collection, save_content)

def func(tuple):
    querystring, type, page = tuple[0], tuple[1], tuple[2]
    url = "http://weixin.sogou.com/weixin"
    # get参数
    data = {
        "query":querystring,
        "type":type,
        "page":page
    }

    titleurl = GetTitleUrl(url, data)

    for item in titleurl:
        url = item["link"]
        print "url:", url
        content = GetContent(url)
        item["content"] = content
        ContentSave(item)


if __name__ == '__main__':
    start = datetime.datetime.now()

    querystring = u"清华"
    type = 2 # 2-文章,1-微信号

    # 多进程抓取
    p = mp.Pool()
    p.map_async(func, [(querystring, type, page) for page in range(1, 50, 1)])
    p.close()
    p.join()

    # # 单进程抓取
    # for page in range(1, 50, 1):
    #     tuple = (querystring, type, page)
    #     func(tuple)

    end = datetime.datetime.now()
    print "last time: ", end-start