分类

类型:
不限 游戏开发 计算机程序开发 Android开发 网站开发 笔记总结 其他
评分:
不限 10 9 8 7 6 5 4 3 2 1
原创:
不限
年份:
不限 2018 2019

技术文章列表

  • MySql高性能优化

    一、表的优化1. 定长与变长分离如id int ,占4个字节,char(4)占4个字符长度,也是定长,
    即,每一个单元值占的字节是固定的;
    核心且常用字段,宜建成定长,放在一张表里;
    而varchar,text,blob,这种变长字段,适合单放一张表,用主键与核心表关联起来。
    2. 常用字段和不常用字段要分离需要结合网站具体的业务来分析,分析字段的查询场景,查询频率低的字段,单拆出来。
    3. 在1对多需要关联统计的字段上,添加冗余字段二、列选择类型1. 字段类型优先级整型>date,time>enum,char>varchar>blob,text
    列的特点分析:
    整型:定长,没有国家/地区之分,没有字符集的差异;
    比如:tinyint 1,2,3,4,5 <—> char(1) a,b,c,d,e
    从空间上,都是占1个字节,但是order by排序,前者快,
    原因:后者需要考虑字符集与校对集(就是排序规则)
    time :定长,运算快,节省空间。考虑时区,写SQL时不方便 where >’2005-10-12’;
    enum: 能起约束值的目的,内部用整型来存储,但与char联查时,内部要经过串与值的转化(如,男女,分别用1,0表示);
    char :定长,考虑字符集和校对集;
    varchar:不定长,要考虑字符集的转换与排序时的校对集,速度慢;
    text/blob:无法使用内存临时表(排序等操作只能在磁盘上进行);
    2. 够用就行,不要慷慨原因:大的字段浪费内存,影响速度;
    以年龄为例:tinyint unsigned not null ,可以存储225岁,对于人来说足够用,如果用int的话就浪费了3个字节;
    以varchar(10),varchar(300)存储的内容相同,但在表联查时,varchar(300)要花更多的内存;
    3. 尽量避免用NULL()原因:NULL不利于索引,要用特殊的字节标注;
    在磁盘上占据的空间其实更大;
    0 留言 2019-12-11 21:21:22 奖励11点积分
  • 炼数成金数据分析课程---16、机器学习中的分类算法

    一、决策树原理决策树是用样本的属性作为结点,用属性的取值作为分支的树结构。
    决策树的根结点是所有样本中信息量最大的属性。树的中间结点是该结点为根的子树所包含的样本子集中信息量最大的属性。决策树的叶结点是样本的类别值。决策树是一种知识表示形式,它是对所有样本数据的高度概括决策树能准确地识别所有样本的类别,也能有效地识别新样本的类别。
    决策树算法ID3的基本思想:
    首先找出最有判别力的属性,把样例分成多个子集,每个子集又选择最有判别力的属性进行划分,一直进行到所有子集仅包含同一类型的数据为止。最后得到一棵决策树。
    J.R.Quinlan的工作主要是引进了信息论中的信息增益,他将其称为信息增益(information gain),作为属性判别能力的度量,设计了构造决策树的递归算法。
    举例子比较容易理解:
    对于气候分类问题,属性为:

    天气(A1) 取值为: 晴,多云,雨
    气温(A2) 取值为: 冷 ,适中,热
    湿度(A3) 取值为: 高 ,正常
    风 (A4) 取值为: 有风, 无风

    每个样例属于不同的类别,此例仅有两个类别,分别为P,N。P类和N类的样例分别称为正例和反例。将一些已知的正例和反例放在一起便得到训练集。
    决策树叶子为类别名,即P 或者N。其它结点由样例的属性组成,每个属性的不同取值对应一分枝。
    若要对一样例分类,从树根开始进行测试,按属性的取值分枝向下进入下层结点,对该结点进行测试,过程一直进行到叶结点,样例被判为属于该叶结点所标记的类别。
    某天早晨气候描述为:

    天气:多云
    气温:冷
    湿度:正常
    风: 无风

    它属于哪类气候呢?——————-从图中可判别该样例的类别为P类。
    ID3就是要从表的训练集构造图这样的决策树。实际上,能正确分类训练集的决策树不止一棵。Quinlan的ID3算法能得出结点最少的决策树。
    ID3算法:

    对当前例子集合,计算各属性的信息增益
    选择信息增益最大的属性Ak
    把在Ak处取值相同的例子归于同一子集,Ak取几个值就得几个子集
    对既含正例又含反例的子集,递归调用建树算法
    若子集仅含正例或反例,对应分枝标上P或N,返回调用处

    一般只要涉及到树的情况,经常会要用到递归。
    对于气候分类问题进行具体计算有:
    ⒈ 信息熵的计算: 其中S是样例的集合, P(ui)是类别i出现概率:
    |S|表示例子集S的总数,|ui|表示类别ui的例子数。对9个正例和5个反例有:
    P(u1)=9/14P(u2)=5/14H(S)=(9/14)log(14/9)+(5/14)log(14/5)=0.94bit⒉ 信息增益的计算:
    其中A是属性,Value(A)是属性A取值的集合,v是A的某一属性值,Sv是S中A的值为v的样例集合,| Sv |为Sv中所含样例数。
    以属性A1为例,根据信息增益的计算公式,属性A1的信息增益为
    S=[9+,5-] //原样例集中共有14个样例,9个正例,5个反例S晴=[2+,3-]//属性A1取值晴的样例共5个,2正,3反S多云=[4+,0-] //属性A1取值多云的样例共4个,4正,0反S雨=[3+,2-] //属性A1取值晴的样例共5个,3正,2反3.结果为
    属性A1的信息增益最大,所以被选为根结点。
    4.建决策树的根和叶子
    ID3算法将选择信息增益最大的属性天气作为树根,在14个例子中对天气的3个取值进行分枝,3 个分枝对应3 个子集,分别是:
    其中S2中的例子全属于P类,因此对应分枝标记为P,其余两个子集既含有正例又含有反例,将递归调用建树算法。
    5.递归建树
    分别对S1和S3子集递归调用ID3算法,在每个子集中对各属性求信息增益.
    (1)对S1,湿度属性信息增益最大,以它为该分枝的根结点,再向下分枝。湿度取高的例子全为N类,该分枝标记N。取值正常的例子全为P类,该分枝标记P。
    (2)对S3,风属性信息增益最大,则以它为该分枝根结点。再向下分枝,风取有风时全为N类,该分枝标记N。取无风时全为P类,该分枝标记P。
    二、PYTHON实现决策树算法分类本代码为machine learning in action 第三章例子,亲测无误。
    1、计算给定数据shangnon数据的函数:
    def calcShannonEnt(dataSet): #calculate the shannon value numEntries = len(dataSet) labelCounts = {} for featVec in dataSet: #create the dictionary for all of the data currentLabel = featVec[-1] if currentLabel not in labelCounts.keys(): labelCounts[currentLabel] = 0 labelCounts[currentLabel] += 1 shannonEnt = 0.0 for key in labelCounts: prob = float(labelCounts[key])/numEntries shannonEnt -= prob*log(prob,2) #get the log value return shannonEnt

    创建数据的函数
    def createDataSet(): dataSet = [[1,1,'yes'], [1,1, 'yes'], [1,0,'no'], [0,1,'no'], [0,1,'no']] labels = ['no surfacing','flippers'] return dataSet, labels
    3.划分数据集,按照给定的特征划分数据集
    def splitDataSet(dataSet, axis, value): retDataSet = [] for featVec in dataSet: if featVec[axis] == value: #abstract the fature reducedFeatVec = featVec[:axis] reducedFeatVec.extend(featVec[axis+1:]) retDataSet.append(reducedFeatVec) return retDataSet
    4.选择最好的数据集划分方式
    def chooseBestFeatureToSplit(dataSet): numFeatures = len(dataSet[0])-1 baseEntropy = calcShannonEnt(dataSet) bestInfoGain = 0.0; bestFeature = -1 for i in range(numFeatures): featList = [example[i] for example in dataSet] uniqueVals = set(featList) newEntropy = 0.0 for value in uniqueVals: subDataSet = splitDataSet(dataSet, i , value) prob = len(subDataSet)/float(len(dataSet)) newEntropy +=prob * calcShannonEnt(subDataSet) infoGain = baseEntropy - newEntropy if(infoGain > bestInfoGain): bestInfoGain = infoGain bestFeature = i return bestFeature
    5.递归创建树
    用于找出出现次数最多的分类名称的函数。
    def majorityCnt(classList): classCount = {} for vote in classList: if vote not in classCount.keys(): classCount[vote] = 0 classCount[vote] += 1 sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True) return sortedClassCount[0][0]
    用于创建树的函数代码
    def createTree(dataSet, labels): classList = [example[-1] for example in dataSet] # the type is the same, so stop classify if classList.count(classList[0]) == len(classList): return classList[0] # traversal all the features and choose the most frequent feature if (len(dataSet[0]) == 1): return majorityCnt(classList) bestFeat = chooseBestFeatureToSplit(dataSet) bestFeatLabel = labels[bestFeat] myTree = {bestFeatLabel:{}} del(labels[bestFeat]) #get the list which attain the whole properties featValues = [example[bestFeat] for example in dataSet] uniqueVals = set(featValues) for value in uniqueVals: subLabels = labels[:] myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels) return myTree
    然后是在python 名利提示符号输入如下命令:
    myDat, labels = trees.createDataSet()myTree = trees.createTree(myDat,labels)print myTree
    结果是:
    {'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}6.实用决策树进行分类的函数
    def classify(inputTree, featLabels, testVec): firstStr = inputTree.keys()[0] secondDict = inputTree[firstStr] featIndex = featLabels.index(firstStr) for key in secondDict.keys(): if testVec[featIndex] == key: if type(secondDict[key]).__name__ == 'dict': classLabel = classify(secondDict[key], featLabels, testVec) else: classLabel = secondDict[key] return classLabel
    在Python命令提示符,输入:
    trees.classify(myTree,labels,[1,0])
    得到结果:
    'no'Congratulation. Oh yeah. You did it.!!!
    1 留言 2019-11-28 10:56:59 奖励15点积分
  • 基于WinInet的HTTPS文件下载实现 精华

    背景如果你之前写过基于WinInet库的HTTP下载文件,那么你在看完本文之后,就会发觉,这是和HTTP文件下载的代码几乎是一模一样的,就是有几个地方的区别而已。但是,本文不是对HTTP和HTTPS在WinInet库中的区别进行总结的,总结就另外写。
    本文就是基于WinInet网络库,实现通过HTTPS传输协议下载文件功能的小程序。现在,就把开发过程的思路和编程分享给大家。
    主要函数介绍介绍HTTPS下载文件使用到的主要的WinInet库中的API函数。
    1. InternetOpen介绍
    函数声明
    HINTERNET InternetOpen(In LPCTSTR lpszAgent,In DWORD dwAccessType,In LPCTSTR lpszProxyName,In LPCTSTR lpszProxyBypass,In DWORD dwFlags);
    参数lpszAgent指向一个空结束的字符串,该字符串指定调用WinInet函数的应用程序或实体的名称。使用此名称作为用户代理的HTTP协议。dwAccessType指定访问类型,参数可以是下列值之一:



    Value
    Meaning




    INTERNET_OPEN_TYPE_DIRECT
    使用直接连接网络


    INTERNET_OPEN_TYPE_PRECONFIG
    获取代理或直接从注册表中的配置,使用代理连接网络


    INTERNETOPEN_TYPE_PRECONFIG WITH_NO_AUTOPROXY
    获取代理或直接从注册表中的配置,并防止启动Microsoft JScript或Internet设置(INS)文件的使用


    INTERNET_OPEN_TYPE_PROXY
    通过代理的请求,除非代理旁路列表中提供的名称解析绕过代理,在这种情况下,该功能的使用



    lpszProxyName指针指向一个空结束的字符串,该字符串指定的代理服务器的名称,不要使用空字符串;如果dwAccessType未设置为INTERNET_OPEN_TYPE_PROXY,则此参数应该设置为NULL。
    lpszProxyBypass指向一个空结束的字符串,该字符串指定的可选列表的主机名或IP地址。如果dwAccessType未设置为INTERNET_OPEN_TYPE_PROXY的 ,参数省略则为NULL。
    dwFlags参数可以是下列值的组合:



    VALUE
    MEANING




    INTERNET_FLAG_ASYNC
    使异步请求处理的后裔从这个函数返回的句柄


    INTERNET_FLAG_FROM_CACHE
    不进行网络请求,从缓存返回的所有实体,如果请求的项目不在缓存中,则返回一个合适的错误,如ERROR_FILE_NOT_FOUND


    INTERNET_FLAG_OFFLINE
    不进行网络请求,从缓存返回的所有实体,如果请求的项目不在缓存中,则返回一个合适的错误,如ERROR_FILE_NOT_FOUND



    返回值成功:返回一个有效的句柄,该句柄将由应用程序传递给接下来的WinInet函数。失败:返回NULL。

    2. InternetConnect介绍
    函数声明
    HINTERNET WINAPI InternetConnect( HINTERNET hInternet, LPCTSTR lpszServerName, INTERNET_PORT nServerPort, LPCTSTR lpszUserName, LPCTSTR lpszPassword, DWORD dwService, DWORD dwFlags, DWORD dwContext);
    参数说明hInternet:由InternetOpen返回的句柄。lpszServerName:连接的ip或者主机名nServerPort:连接的端口。lpszUserName:用户名,如无置NULL。lpszPassword:密码,如无置NULL。dwService:使用的服务类型,可以使用以下

    INTERNET_SERVICE_FTP = 1:连接到一个 FTP 服务器上INTERNET_SERVICE_GOPHER = 2INTERNET_SERVICE_HTTP = 3:连接到一个 HTTP 服务器上
    dwFlags:文档传输形式及缓存标记。一般置0。dwContext:当使用回叫信号时, 用来识别应用程序的前后关系。返回值成功返回非0。如果返回0。要InternetCloseHandle释放这个句柄。

    3. HttpOpenRequest介绍
    函数声明
    HINTERNET HttpOpenRequest( _In_ HINTERNET hConnect, _In_ LPCTSTR lpszVerb, _In_ LPCTSTR lpszObjectName, _In_ LPCTSTR lpszVersion, _In_ LPCTSTR lpszReferer, _In_ LPCTSTR *lplpszAcceptTypes, _In_ DWORD dwFlags, _In_ DWORD_PTR dwContext);
    参数
    hConnect:由InternetConnect返回的句柄。
    lpszVerb:一个指向某个包含在请求中要用的动词的字符串指针。如果为NULL,则使用“GET”。
    lpszObjectName:一个指向某个包含特殊动词的目标对象的字符串的指针。通常为文件名称、可执行模块或者查找标识符。
    lpszVersion:一个指向以null结尾的字符串的指针,该字符串包含在请求中使用的HTTP版本,Internet Explorer中的设置将覆盖该参数中指定的值。如果此参数为NULL,则该函数使用1.1或1.0的HTTP版本,这取决于Internet Explorer设置的值。
    lpszReferer:一个指向指定了包含着所需的URL (pstrObjectName)的文档地址(URL)的指针。如果为NULL,则不指定HTTP头。
    lplpszAcceptTypes:一个指向某空终止符的字符串的指针,该字符串表示客户接受的内容类型。如果该字符串为NULL,服务器认为客户接受“text/*”类型的文档 (也就是说,只有纯文本文档,并且不是图片或其它二进制文件)。内容类型与CGI变量CONTENT_TYPE相同,该变量确定了要查询的含有相关信息的数据的类型,如HTTP POST和PUT。
    dwFlags:dwFlags的值可以是下面一个或者多个。



    价值
    说明




    INTERNET_FLAG_DONT_CACHE
    不缓存的数据,在本地或在任何网关。 相同的首选值INTERNET_FLAG_NO_CACHE_WRITE。


    INTERNET_FLAG_EXISTING_CONNECT
    如果可能的话,重用现有的连接到每个服务器请求新的请求而产生的InternetOpenUrl创建一个新的会话。 这个标志是有用的,只有对FTP连接,因为FTP是唯一的协议,通常在同一会议执行多个操作。 在Win 32 API的缓存一个单一的Internet连接句柄为每个HINTERNET处理产生的InternetOpen。


    INTERNET_FLAG -超链接
    强制重载如果没有到期的时间也没有最后修改时间从服务器在决定是否加载该项目从网络返回。


    INTERNET_FLAG_IGNORE_CERT_CN_INVALID
    禁用的Win32上网功能的SSL /厘为基础的打击是从给定的请求服务器返回的主机名称证书检查。 Win32的上网功能用来对付证书由匹配主机名和HTTP请求一个简单的通配符规则比较简单的检查。


    INTERNET_FLAG_IGNORE_CERT_DATE_INVALID
    禁用的Win32上网功能的SSL /厘为基础的HTTP请求适当的日期,证书的有效性检查。


    INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP
    禁用的Win32上网功能能够探测到这种特殊类型的重定向。 当使用此标志,透明的Win32上网功能允许对HTTP重定向的URL从HTTPS。


    INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS
    禁用的Win32上网功能能够探测到这种特殊类型的重定向。 当使用此标志,透明的Win32上网功能允许从HTTP重定向到HTTPS网址。


    INTERNET_FLAG_KEEP_CONNECTION
    使用保持活动语义,如果有的话,给HTTP请求连接。 这个标志是必需的微软网络(MSN),NT LAN管理器(NTLM)和其他类型的身份验证。


    INTERNET_FLAG_MAKE_PERSISTENT
    不再支持。


    INTERNET_FLAG_MUST_CACHE_REQUEST
    导致一个临时文件如果要创建的文件不能被缓存。 相同的首选值INTERNET_FLAG_NEED_FILE。


    INTERNET_FLAG_NEED_FILE
    导致一个临时文件如果要创建的文件不能被缓存。


    INTERNET_FLAG_NO_AUTH
    不尝试HTTP请求身份验证自动。


    INTERNET_FLAG_NO_AUTO_REDIRECT
    不自动处理HTTP请求重定向只。


    INTERNET_FLAG_NO_CACHE_WRITE
    不缓存的数据,在本地或在任何网关。


    INTERNET_FLAG_NO_COOKIES
    不会自动添加Cookie标头的请求,并不会自动添加返回的Cookie的HTTP请求的Cookie数据库。


    INTERNET_FLAG_NO_UI
    禁用cookie的对话框。


    INTERNET_FLAG_PASSIVE
    使用被动FTP语义FTP文件和目录。


    INTERNET_FLAG_RAW_DATA
    返回一个数据WIN32_FIND_DATA结构时,FTP目录检索信息。 如果这个标志,或者未指定代理的电话是通过一个CERN,InternetOpenUrl返回的HTML版本的目录。


    INTERNET_FLAG_PRAGMA_NOCACHE
    强制要求被解决的原始服务器,即使在代理缓存的副本存在。


    INTERNET_FLAG_READ_PREFETCH
    该标志目前已停用。


    INTERNET_FLAG_RELOAD
    从导线获取数据,即使是一个本地缓存。


    INTERNET_FLAG_RESYNCHRONIZE
    重整HTTP资源,如果资源已被修改自上一次被下载。 所有的FTP资源增值。


    INTERNET_FLAG_SECURE
    请确保在使用SSL或PCT线交易。 此标志仅适用于HTTP请求。



    dwContext:OpenRequest操作的上下文标识符。

    4. InternetReadFile介绍
    函数声明
    BOOL InternetReadFile( __in HINTERNET hFile,__out LPVOID lpBuffer,__in DWORD dwNumberOfBytesToRead,__out LPDWORD lpdwNumberOfBytesRead);
    参数

    hFile[in]
    由InternetOpenUrl,FtpOpenFile, 或HttpOpenRequest函数返回的句柄.
    lpBuffer[out]
    缓冲器指针
    dwNumberOfBytesToRead[in]
    欲读数据的字节量。
    lpdwNumberOfBytesRead[out]
    接收读取字节量的变量。该函数在做任何工作或错误检查之前都设置该值为零

    返回值成功:返回TRUE,失败,返回FALSE

    程序设计原理该部分讲解下程序设计的原理以及实现的流程,让大家有个宏观的认识。原理是:

    首先,使用 InternetCrackUrl 函数分解URL,从URL中提取网站的域名、路径以及URL的附加信息等。关于 InternetCrackUrl 分解URL的介绍和实现,可以参考 “URL分解之InternetCrackUrl” 这篇文章
    使用 InternetOpen 建立会话,获取会话句柄
    使用 InternetConnect 与网站建立连接,获取连接句柄
    设置HTTPS的访问标志,使用 HttpOpenRequest 打开HTTP的“GET”请求
    使用 HttpSendRequest 发送访问请求,同时根据出错返回的错误码,来判断是否设置忽略未知的证书颁发机构,以确保能正常访问HTTPS网站
    根据返回的Response Header的数据中,获取将要接收数据的长度
    使用 InternetReadFile 接收数据
    关闭句柄,释放资源

    其中,上面的 8 个步骤中,要注意第 4 步访问标志的标志设置;注意第 5 步的忽略未知的证书颁发机构的设置;同时,还要注意的就是第 6 步,获取返回的数据长度,是从响应信息头中的获取“Content-Length: ”(注意有个空格)这个字段的数据。
    编程实现1. 导入WinInet库#include <WinInet.h>#pragma comment(lib, "WinInet.lib")
    2. HTTPS文件下载编程实现// 数据下载// 输入:下载数据的URL路径// 输出:下载数据内容、下载数据内容长度BOOL Https_Download(char *pszDownloadUrl, BYTE **ppDownloadData, DWORD *pdwDownloadDataSize){ // INTERNET_SCHEME_HTTPS、INTERNET_SCHEME_HTTP、INTERNET_SCHEME_FTP等 char szScheme[MAX_PATH] = {0}; char szHostName[MAX_PATH] = { 0 }; char szUserName[MAX_PATH] = { 0 }; char szPassword[MAX_PATH] = { 0 }; char szUrlPath[MAX_PATH] = { 0 }; char szExtraInfo[MAX_PATH] = { 0 }; ::RtlZeroMemory(szScheme, MAX_PATH); ::RtlZeroMemory(szHostName, MAX_PATH); ::RtlZeroMemory(szUserName, MAX_PATH); ::RtlZeroMemory(szPassword, MAX_PATH); ::RtlZeroMemory(szUrlPath, MAX_PATH); ::RtlZeroMemory(szExtraInfo, MAX_PATH); // 分解URL if (FALSE == Https_UrlCrack(pszDownloadUrl, szScheme, szHostName, szUserName, szPassword, szUrlPath, szExtraInfo, MAX_PATH)) { return FALSE; } // 数据下载 HINTERNET hInternet = NULL; HINTERNET hConnect = NULL; HINTERNET hRequest = NULL; DWORD dwOpenRequestFlags = 0; BOOL bRet = FALSE; unsigned char *pResponseHeaderIInfo = NULL; DWORD dwResponseHeaderIInfoSize = 2048; BYTE *pBuf = NULL; DWORD dwBufSize = 64*1024; BYTE *pDownloadData = NULL; DWORD dwDownloadDataSize = 0; DWORD dwRet = 0; DWORD dwOffset = 0; do { // 建立会话 hInternet = ::InternetOpen("WinInetGet/0.1", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); if (NULL == hInternet) { Https_ShowError("InternetOpen"); break; } // 建立连接(与HTTP的区别 -- 端口) hConnect = ::InternetConnect(hInternet, szHostName, INTERNET_DEFAULT_HTTPS_PORT, szUserName, szPassword, INTERNET_SERVICE_HTTP, 0, 0); if (NULL == hConnect) { Https_ShowError("InternetConnect"); break; } // 打开并发送HTTPS请求(与HTTP的区别--标志) dwOpenRequestFlags = INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP | INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_NO_AUTH | INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_NO_UI | // HTTPS SETTING INTERNET_FLAG_SECURE | INTERNET_FLAG_IGNORE_CERT_CN_INVALID | INTERNET_FLAG_RELOAD; if (0 < ::lstrlen(szExtraInfo)) { // 注意此处的连接 ::lstrcat(szUrlPath, szExtraInfo); } hRequest = ::HttpOpenRequest(hConnect, "GET", szUrlPath, NULL, NULL, NULL, dwOpenRequestFlags, 0); if (NULL == hRequest) { Https_ShowError("HttpOpenRequest"); break; } // 发送请求(与HTTP的区别--对无效的证书颁发机构的处理) bRet = ::HttpSendRequest(hRequest, NULL, 0, NULL, 0); if (FALSE == bRet) { if (ERROR_INTERNET_INVALID_CA == ::GetLastError()) { DWORD dwFlags = 0; DWORD dwBufferSize = sizeof(dwFlags); // 获取INTERNET_OPTION_SECURITY_FLAGS标志 bRet = ::InternetQueryOption(hRequest, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, &dwBufferSize); if (bRet) { // 设置INTERNET_OPTION_SECURITY_FLAGS标志 // 忽略未知的证书颁发机构 dwFlags = dwFlags | SECURITY_FLAG_IGNORE_UNKNOWN_CA; bRet = ::InternetSetOption(hRequest, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof(dwFlags)); if (bRet) { // // 再次发送请求 bRet = ::HttpSendRequest(hRequest, NULL, 0, NULL, 0); if (FALSE == bRet) { Https_ShowError("HttpSendRequest"); break; } } else { Https_ShowError("InternetSetOption"); break; } } else { Https_ShowError("InternetQueryOption"); break; } } else { Https_ShowError("HttpSendRequest"); break; } } // 接收响应的报文信息头(Get Response Header) pResponseHeaderIInfo = new unsigned char[dwResponseHeaderIInfoSize]; if (NULL == pResponseHeaderIInfo) { break; } ::RtlZeroMemory(pResponseHeaderIInfo, dwResponseHeaderIInfoSize); bRet = ::HttpQueryInfo(hRequest, HTTP_QUERY_RAW_HEADERS_CRLF, pResponseHeaderIInfo, &dwResponseHeaderIInfoSize, NULL); if (FALSE == bRet) { Https_ShowError("HttpQueryInfo"); break; }#ifdef _DEBUG printf("[HTTPS_Download_ResponseHeaderIInfo]\n\n%s\n\n", pResponseHeaderIInfo);#endif // 从 中字段 "Content-Length: "(注意有个空格) 获取数据长度 bRet = Https_GetContentLength((char *)pResponseHeaderIInfo, &dwDownloadDataSize); if (FALSE == bRet) { break; } // 接收报文主体内容(Get Response Body) pBuf = new BYTE[dwBufSize]; if (NULL == pBuf) { break; } pDownloadData = new BYTE[dwDownloadDataSize]; if (NULL == pDownloadData) { break; } ::RtlZeroMemory(pDownloadData, dwDownloadDataSize); do { ::RtlZeroMemory(pBuf, dwBufSize); bRet = ::InternetReadFile(hRequest, pBuf, dwBufSize, &dwRet); if (FALSE == bRet) { Https_ShowError("InternetReadFile"); break; } ::RtlCopyMemory((pDownloadData + dwOffset), pBuf, dwRet); dwOffset = dwOffset + dwRet; } while (dwDownloadDataSize > dwOffset); // 返回数据 *ppDownloadData = pDownloadData; *pdwDownloadDataSize = dwDownloadDataSize; } while (FALSE); // 关闭 释放 if (NULL != pBuf) { delete[]pBuf; pBuf = NULL; } if (NULL != pResponseHeaderIInfo) { delete[]pResponseHeaderIInfo; pResponseHeaderIInfo = NULL; } if (NULL != hRequest) { ::InternetCloseHandle(hRequest); hRequest = NULL; } if (NULL != hConnect) { ::InternetCloseHandle(hConnect); hConnect = NULL; } if (NULL != hInternet) { ::InternetCloseHandle(hInternet); hInternet = NULL; } return bRet;}
    程序测试在main函数中,调用上述封装好的函数,下载文件进行测试。
    main函数为:
    int _tmain(int argc, _TCHAR* argv[]){ char szHttpsDownloadUrl[] = "https://download.microsoft.com/download/0/2/3/02389126-40A7-46FD-9D83-802454852703/vc_mbcsmfc.exe"; BYTE *pHttpsDownloadData = NULL; DWORD dwHttpsDownloadDataSize = 0; // HTTPS下载 if (FALSE == Https_Download(szHttpsDownloadUrl, &pHttpsDownloadData, &dwHttpsDownloadDataSize)) { return 1; } // 将数据保存成文件 Https_SaveToFile("https_downloadsavefile.exe", pHttpsDownloadData, dwHttpsDownloadDataSize); // 释放内存 delete []pHttpsDownloadData; pHttpsDownloadData = NULL; system("pause"); return 0;}
    测试结果:
    根据返回的Response Header知道,成功下载67453208字节大小的数据。

    查看目录,有65873KB大小的“https_downloadsavefile.zip”文件成功生成,所以,数据下载成功。

    总结基于 WinInet 库的 HTTPS 下载文件原理并不复杂,但是,因为涉及较多的 API,每个 API 的执行都需要依靠上一个 API 成功执行返回的数据。所以,要仔细检查。如果出错,也要耐心调试,根据返回的错误码,结合程序前后部分的代码,仔细分析原因。
    同时要注意在使用 HttpOpenRequest 和 HttpSendRequest 函数中,对 HTTPS 的标志设置以及忽略未知的证书颁发机构。
    参考参考自《Windows黑客编程技术详解》一书
    2 留言 2018-12-22 10:15:55 奖励15点积分
  • 基于ASP.NET的小清新风格的新闻发布系统

    项目展示
    项目开发背景
    ASP.NET课设
    主要功能:登录注册,新闻管理,评论回复新闻,个人信息管理

    项目运行环境
    vs2017
    Sqlserver2012

    项目运行安装
    项目包地址:https://download.csdn.net/download/qq_35268841/11865696
    项目数据库位置由于大家所用数据库不一定为2012,在此将数据库的Sql文件放出,地址如下:
    链接: https://pan.baidu.com/s/170lyOlqnE2B_Eu30WOlpMA 提取码: km8k 复制这段内容后打开百度网盘手机App,操作更方便哦

    配置数据库连接字符串(不会获取连接字符串的自行百度)https://blog.csdn.net/qq_35268841/article/details/102563070

    运行项目
    浏览器显示网页主页(网页不是响应式布局,若网页显示不整齐,缩放浏览器比例即可)
    0 留言 2019-12-07 00:45:51 奖励16点积分
  • 利用网页打开本地exe文件

    首次接到“利用网页打开本地exe文件”这个任务时,还真有点摸不着头脑,后来细想,在淘宝上点击卖家的旺旺能开启本地的旺旺,这不就是利用的网页打开本地exe文件吗?
    了解这种实实在在存在的合理需求后,开始调查。经过网上搜索查询,主要归纳为两种实现方式,方式一:利用JS打开本地exe文件。一般浏览器,由于安全问题,都会禁止掉这个特性,这就导致部分浏览器不支持该种方式。方式二:利用浏览器外部协议(URL Procotol)打开本地exe文件。用这种方式实现,任何浏览器都兼容。在实际开发中,当然首选方式二。
    一、利用注册表文件将外部协议写入注册表[HKEY_CLASSES_ROOT\PCTV]@="PCTVProtocol""URL Protocol"="\"C:\\Program Files (x86)\\PCTV双模软终端_64位\\PCTV.exe\""[HKEY_CLASSES_ROOT\PCTV\DefaultIcon]@="\"C:\\Program Files (x86)\\PCTV双模软终端_64位\\PCTV.exe,1\"" [HKEY_CLASSES_ROOT\PCTV\shell] [HKEY_CLASSES_ROOT\PCTV\shell\open] [HKEY_CLASSES_ROOT\PCTV\shell\open\command]@="\"C:\\Program Files (x86)\\PCTV双模软终端_64位\\PCTV.exe\" \"%1\""将以上代码存入reg文件中,双击文件执行即可。在浏览器中输入 “pctv://” 或 “pctv://param1,param2” ,执行后即可打开对应路径下的exe文件。
    二、 在安装exe文件时将外部协议写入注册表在实际部署中,不会让客户安装完程序再手动单击注册表文件将安装路径写入注册表,最容易让人接受的方式就是在安装exe文件时将安装路径写入注册表。利用Inno Setup打包exe文件时,在脚本中加入如下代码即可:
    [Registry] Root:HKCR;Subkey:"PCTV";ValueType:string;ValueName:"URL Protocol";ValueData:"{app}\{#MyAppExeName}";Flags:createvalueifdoesntexist uninsdeletekeyRoot:HKCR;Subkey:"PCTV\DefaultIcon";ValueType:string;ValueData:"{app}\{#MyAppExeName}";Flags:createvalueifdoesntexist uninsdeletekey Root:HKCR;Subkey:"PCTV\shell";Flags:createvalueifdoesntexist uninsdeletekeyRoot:HKCR;Subkey:"PCTV\shell\open";Flags:createvalueifdoesntexist uninsdeletekeyRoot:HKCR;Subkey:"PCTV\shell\open\command";ValueType:string;ValueData:"{app}\{#MyAppExeName} ""%1""";Flags:createvalueifdoesntexist uninsdeletekey这样,在浏览器中输入 “pctv://” 或 “pctv://param1,param2” ,执行后即可打开对应路径下的exe文件。
    以上两种写入注册的方式,允许在外部协议中带参数。
    三、遇到的问题在利用外部协议打开本地exe文件时,通过查看日志记录,看到会出现路径不对的问题。通过查看代码在程序中用Environment.CurrentDirectory获取可执行文件的路径,但是通过浏览器打开exe文件时,Environment.CurrentDirectory获取的是浏览器exe文件的路径,这样在程序中就会报错。解决方法是将Environment.CurrentDirectory修改为Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory)即可。
    0 留言 2019-12-06 11:30:53 奖励15点积分
  • PC微信逆向分析のWeTool内部探秘 精华

    作者:zmrbak(赵庆明老师)
    前言先不说微信在社交领域的霸主地位,我们仅从腾讯公司所透露的在研发微信过程中踩过的无数的坑,以及公开的与微信相关的填坑的源码中,我们可以感受到,单从技术上讲,微信是一款非常伟大的产品。然而,伟大的产品,往往会被痴迷于技术的人送进实验室,运用各种可能的工具将其大卸八块,以参悟其“伟大”之所在!。
    WeTool,一款免费的微信社群管理工具,正是一群痴迷于技术的人对于微信这个伟大的产品的研究而得到的成果。在微商界,这个软件真可谓是鼎鼎大名、如雷贯耳。如果你还不知晓这个软件,那么你肯定不是微商界的人。如果你想对你的微信群进行管理,而又不想花钱,也许这个软件就是你最佳的选择。当然,免费软件的套路都是一样的,WeTool“有意地”不满足你的一些特殊需求,如果真的很想要的话,当然是要付费的,那就购买“企业版”吧。
    但是,对于一个对技术有强烈兴趣的人来说,研究WeTool与研究PC微信一样有趣,在这里,我把它们两个一起送进实验室,一窥其中的奥秘!
    微信中的WeTool由于腾讯干预,目前WeTool免费版本已不再公开提供下载。但之前的旧版本仍然可以自动升级到最新版。如果你想获得WeTool这个软件,我想,你应该知道该怎么做了吧。如果你还是不知道,很抱歉,这篇文章对你来说太深奥了。那么我对你的建议是:关掉这个网页吧。
    WeTool在启动的时候,会检查当前计算机上是否安装了版本匹配的PC微信。倘若找不到,或者版本不匹配,WeTool会引导你到它的官网去下载一个版本匹配的PC微信(可能比较旧,但能用)。下载完毕后,还需要你手动去安装一下。
    在WeTool启动的时候,还会检查微信的登录状态,如果微信还未完成登录,WeTool会等待微信登录之后,再开启自己的管理界面。
    这里的问题是:WeTool是如何得知微信是否已经登录了呢?
    在这里,我们使用PCHunter来检查一下微信(WeChat.exe)的进程模块。我们可以看到,在微信的进程中加载了一个特殊的DLL文件(WeHelp.dll),而它的父目录是一个特殊的字符串:“2.6.8.65”,恰好与我们当前运行的微信版本一致。再上一层的目录,“WeToolCore”,很明显,这里的文件是WeTool的一部分。

    恰恰是这个DLL文件帮助WeTool完成了与微信之间的各种互动。也就是说,WeTool通过WeHelp.dll这个文件,可以感知到微信的各种活动,当然也包括微信是否已经登录等等…
    窥探WeTool如果在不经意之间关闭了WeTool,你会发现,你的微信也被关闭了。这又是为什么呢?
    如果你曾经用OD调试过软件,你会发现当你的OD被关闭的时候,被OD所调试的那个软件也被关闭掉了。因此,我们猜想,WeTool对于微信来说,应该使用的是类似于OD之于其他软件相同的原理,那就是“调试”。
    在WeTool管理你的微信的时候,你也会发现,这时候微信无法被OD所附加。其实,还是“调试”。当一个软件已经处于某个调试器的“调试”之下,为了防止出错,调试器会拒绝对这个已处于被调试中的软件的再次调试。这进一步印证了WeTool对于微信的“调试”的事实。
    然而就是这么一个“小小的”设置,就击碎不少“小白”想调试WeTool美梦。
    既然我们找到了WeTool对于微信的关键,那就是文件“WeHelp.dll”。那么,我们就把这个文件请入我们的实验室,让我们把它一点一点地拆开,细细探寻其中的一点一滴的奥秘。
    拆解WeTool在动手拆解之前,我们还是先了解一下WeTool到底向我们的计算机上安装了些什么东东。顺着桌面上的“WeTool 免费版”,我们找到了WeTool安装的目录,安装目录之下22个文件夹和84个文件。当然,让我们比较感兴趣的就是“WeChatVersion”这个文件夹,因为它的名字与微信(WeChat)太让人能联想到一起了。

    双击“WeChatVersion”,我们看到如下结果。恰好是以微信曾经的一个个版本号命名的文件夹。我们猜想,这个文件夹一定与这个版本的微信之间存在中某种联系。目前,我们可以得到最新的微信版本是2.6.8.68(此版本为更新版;从腾讯官网可下载到的版本仅为2.6.8.65),而这里恰好有一个以该版本号命名的文件夹“2.6.8.65”。

    我们双击打开“2.6.8.65”这个文件夹。文章前面所提到的“WeHelp.dll”文件赫然在目。点开其他类似微信版本号的文件夹,同样,每个文件夹中都有这两个文件。唯一的区别就是文件的大小不一样。
    由于我们使用的微信版本是2.6.8.65,那么我们就针对2.6.8.65文件夹下的这个“WeHelp.dll”进行研究。通过二进制对比,我们发现该文件夹下的“WeHelp.dll”文件与微信中加载的“WeHelp.dll” 文件为同一个文件。

    由此,我们得出结论:WeTool为不同版本的微信分别提供了不同的WeHelp.dll文件,在WeTool启动的时候,把WeChatVersion中对应与当前版本微信号的文件夹复制到当前Windows登录用户的应用程序数据文件夹中,然后再将里面的“WeHelp.dll”加载到微信进程的内存中。
    WeHelp解析WeTool为“WeHelp.dll”设置了一道阻止“动态调试”的障碍,这足以让所有的动态调试器,在没有特殊处理前,对它根本无法下手。
    如果能绕道而行,那何必强攻呢?于是我们请出静态分析的利器——IDA PRO 32。注意,这里务必使用32位版本的,因为只有在32位版本中,才可以把汇编代码转换成C语言的伪代码。相比于汇编代码来说,C代码就直观的多了。
    打开IDA,点击按钮“GO”,然后把WeHelp.dll拖入其中,接下来就是十几秒的解析,解析完毕后,界面如下:

    从IDA解析的结果中,让我们很惊奇的是,在“WeHelp.dll”中居然未发现什么加壳啊、加密啊、混淆啊等等这些对于程序版权保护的技术。也许是WeTool太自信了吧!毕竟WeTool是事实上的业界老大,其地位无人可以撼动。
    对于和微信之间交互的这部分功能来说,其实对于一个刚入门的、比较勤奋的逆向新手,只需经过半年到一年时间的练手,这部分功能也是可以完成。对于WeTool来说,其真正的核心价值不在这里,而在于其“正向”的管理逻辑,以及自己后台的Web服务器。在它的管理界面,各种功能实现里逻辑错综复杂,如果你想逆向的话,还不如重写算了,况且它都已经免费给你用了,还有必要逆向吗!!当然,WeTool后台的服务器,你根本就碰不到。
    从IDA解析的结果中,可以看到WeHelp中各个函数、方法,毫无遮拦地完全展示在眼前。而在右侧的窗口中,按下F5,瞬间汇编代码变成了C语言的伪代码。

    对于一个稍稍有一些Window API编程经验的人来说,这些全部都是似曾相识的C代码,只需简单地猜一猜,就能看明白写的是啥。如果还是不懂的话,那就打开Visual Studio,对照着看吧。这里是DllMain,也就是DLL的入口函数。我们还是来创建一个C++的动态链接库(dll)的项目,来对照着看吧:

    fdwReason=1,恰好,DLL_PROCESS_ATTACH=1。一旦DLL被加载,则马上执行DllMain这个函数中的DLL_PROCESS_ATTACH分支。也就是说,当“WeHelp.dll”这个文件被微信加载到进程之后,马上执行一下DllMain函数,DLL_PROCESS_ATTACH分支里面的这两个函数就会马上执行。

    鼠标双击第一个函数(sub_10003040),到里面去看看这个函数里面有啥,如下图,它的返回值来自于一个Windows Api(桃红色字体)——“RegisterWindowMessageW”,查看MSDN后,发现,原来是注册Windows消息。
    这不是我们最想要的,按ESC键,返回。
    鼠标双击下一个函数(sub_100031B0),页面变成这个啦。很明显,在注册一个窗口类。对于一个窗口来说,最重要的就是它的回调函数,因为要在回调函数中,完成对窗口的所有事件处理。这里,lpfnWndProc= sub_10003630,就很明显了,这就是回调函数。

    双击sub_10003630这个函数,窗口切换为如下内容。除了第一条语句的if之外,剩下的if…else if…else if是那么的引人注目。每一个比较判断之后,都调用了一个函数。而判断的依据是传入的参数“lParam”要与一个dword的值比较。
    我们猜测,这些函数大概是WeHelp和微信之间交互相关的函数吧。当然,这只是猜测,我们还要进一步验证才行。

    sub_10003630这个函数,是窗口的回调函数,我们要重点关注。那么,我们先给它改个名字吧。在函数名上点右键,选中“Rename global item”,我们取个名字叫“Fn_WndProc”吧。于是页面就变成了这样:

    虽然在IDA中,“WeHelp.dll”中的函数(方法)全部显示出来了,但是也有40多个呢,我们找个简单一点的来试试。CWeHelp::Logout(void),这个函数没有参数,那么我们就选这个吧。在左侧函数窗口中双击CWeHelp::Logout(void),右侧窗口换成了这个函数的C语言伪代码(如果你显示的还是汇编,请点击汇编代码后按F5)。

    在前面,我们看到,在回调函数中,lParam要与一个dword值进行比较。在这个函数中,我们发现,这里为lParam赋了一个dword类型的值。为了方便记忆,我们把这个dowrd值改个名字吧,因为是Logout函数中用到的数字,那么就叫做“D_Logout”吧。

    接下来,我们要看看”还有谁”在用这个数值。在我们修改后的“D_Logout”上点右键,选择“Jump to xref…”。原来这个数值只有两个地方使用,一个就是当前的“Logout”函数,而另一个却是在”Fn_WndProc”中,那不就是前面的那个回调函数嘛!选中“Fn_WndProc”这一行,点击OK!

    又一次看到了熟悉的if…else if…,还有和“D_Logout”进行比较的分支,而这个分支里面只调用了一个函数sub_10005940,而且不带参数。

    双击函数“sub_10005940”后,发现这个函数很简单。核心语句只有两条,首先调用了sub_100030F0函数,然后得到一个返回值。接下来,为这个返回值加上一个数值“0x3F2BF0”,再转换成一个函数指针,再给一个0作为参数,再调用这个函数指针。最后返回结果。

    我们这里要关注,来自于“sub_100030F0”函数的返回值result到底是什么?
    同样,双击这个函数(sub_100030F0),进去看看呗!原来,调用了一个Windows API函数(GetModuleHandleW),查看MSDN后,发现原来这个函数的功能就是取微信的基址(WeChatWin.dll)。

    那就简单多了!是不是说,如果我们在微信中执行“((int (__stdcall *)(_DWORD))(weChatWinBaseAddress + 0x3F2BF0))(0);”这么一句代码,就可以实现Logout功能呢?
    当然,这只是猜测,我们还需要进一步验证。
    猜测验证打开原来创建的C++的动态链接库项目,把 case分支DLL_PROCESS_ATTACH换成如下内容:
    case DLL_PROCESS_ATTACH: { DWORD weChatWinBaseAddress = (int)GetModuleHandleW(L"WeChatWin.dll"); ((int(__stdcall*)(DWORD))(weChatWinBaseAddress + 0x3F2BF0))(0); }
    注意:把从IDA中拷贝过来的代码中 “_DWORD”中的下划线去掉,就可以编译通过了。

    启动微信,登录微信(这时候,手机微信中会显示“Windows微信已登录”)。
    使用OD附加微信(确保WeTool已经退出,否则OD附加不成功)。
    在OD汇编代码窗口点右键,选择”StrongOD\InjectDll\Remote Thread”,选中刚才Visual Studio中编译成功的那个dll文件。

    一秒钟!OK,神奇的事情发生了:微信提示,你已退出微信!
    同时,手机微信上原来显示的 “Windows微信已登录”,也消失了。
    从这里我们可以确定,微信“真的”是退出了,而不是崩掉了。

    总结其实逆向研究,并不只是靠苦力,更重要的是强烈的好奇心和发散的思维。也许一个瞬间,换一下思维模式,瞬间一切都开朗了。一个人的力量是有限的,融入一个圈子,去借鉴别人的成功经验,同时贡献自己成功经验,你会发现,逆向研究其实是一件非常有趣的事情。当然,我研究的后编写源码都是免费公开的,你可以到GitHub(https://github.com/zmrbak/PcWeChatHooK)上下载,也欢迎和我们一起学习和研究。点击入群:456197310
    后记2019年3月17日前,本人对WeTool进行过一些探索,始终没有取得进展。时隔两个月之后,突然有所发现,于是在2019年5月29日将我的探索成果录制成一个个视频,分享给和我一起研究和探索微信的好朋友。结果,在他们中激起了强烈的反响,有不少的朋友的研究进度有了一个飞跃性的发展,还有几个朋友短短几日之间,就远远超越了我的研究进度。看到这样的场景,让我的内心充满欢喜。当然,还有不少朋友的评价,更是让我开心,现摘录如下:











    源码分享:https://github.com/zmrbak/PcWeChatHooK
    2 留言 2019-09-14 15:14:33 奖励35点积分
  • Http Server实现原理解读

    Tinyhttpd 是 J. David Blackstone 在1999年写的一个不到 500 行的超轻量型 Http Server,用来学习非常不错,可以帮助我们真正理解服务器程序的本质。官网:http://tinyhttpd.sourceforge.net
    每个函数的作用处理从套接字上监听到的一个 HTTP 请求,在这里可以很大一部分地体现服务器处理请求流程。

    bad_request:返回给客户端这是个错误请求,HTTP 状态吗 400 BAD REQUEST
    cat:读取服务器上某个文件写到 socket 套接字
    cannot_execute:主要处理发生在执行 cgi 程序时出现的错误
    error_die:把错误信息写到 perror 并退出
    execute_cgi:运行 cgi 程序的处理,也是个主要函数
    get_line:读取套接字的一行,把回车换行等情况都统一为换行符结束
    headers:把 HTTP 响应的头部写到套接字
    not_found:主要处理找不到请求的文件时的情况
    sever_file:调用 cat 把服务器文件返回给浏览器
    startup:初始化 httpd 服务,包括建立套接字,绑定端口,进行监听等
    unimplemented:返回给浏览器表明收到的 HTTP 请求所用的 method 不被支持

    建议源码阅读顺序:main —> startup —> accept_request —> execute_cgi,通晓主要工作流程后再仔细把每个函数的源码看一看。
    工作流程1) 服务器启动,在指定端口或随机选取端口绑定 httpd 服务
    2) 收到一个 HTTP 请求时(其实就是 listen 的端口 accpet 的时候),派生一个线程运行 accept_request 函数
    3) 取出 HTTP 请求中的 method (GET 或 POST) 和 url,。对于 GET 方法,如果有携带参数,则 query_string 指针指向 url 中 ?后面的 GET 参数
    4) 格式化 url 到 path 数组,表示浏览器请求的服务器文件路径,在 tinyhttpd 中服务器文件是在 htdocs 文件夹下。当 url 以 / 结尾,或 url 是个目录,则默认在 path 中加上 index.html,表示访问主页
    5) 如果文件路径合法,对于无参数的 GET 请求,直接输出服务器文件到浏览器,即用 HTTP 格式写到套接字上,跳到(10)。其他情况(带参数 GET,POST 方式,url 为可执行文件),则调用 excute_cgi 函数执行 cgi 脚本
    6) 读取整个 HTTP 请求并丢弃,如果是 POST 则找出 Content-Length. 把 HTTP 200 状态码写到套接字
    7) 建立两个管道,cgi_input 和 cgi_output, 并 fork 一个进程
    8) 在子进程中,把 STDOUT 重定向到 cgi_outputt 的写入端,把 STDIN 重定向到 cgi_input 的读取端,关闭 cgi_input 的写入端 和 cgi_output 的读取端,设置 request_method 的环境变量,GET 的话设置 query_string 的环境变量,POST 的话设置 content_length 的环境变量,这些环境变量都是为了给 cgi 脚本调用,接着用 execl 运行 cgi 程序
    9) 在父进程中,关闭 cgi_input 的读取端 和 cgi_output 的写入端,如果 POST 的话,把 POST 数据写入 cgi_input,已被重定向到 STDIN,读取 cgi_output 的管道输出到客户端,该管道输入是 STDOUT。接着关闭所有管道,等待子进程结束。这一部分比较乱,见下图说明:
    管道初始状态

    管道最终状态

    10) 关闭与浏览器的连接,完成了一次 HTTP 请求与回应,因为 HTTP 是无连接的
    2 留言 2019-04-24 10:50:21 奖励15点积分
  • 基于MuPDF库实现PDF文件转换成PNG格式图片 精华

    背景之所以会接触MuPDF是因为,有位群友在Q群里提问,如何将PDF保存为.PNG图片格式。我一看到这个问题,就蒙了,因为我没有接触过类似的项目或程序。但是,作为一群之主的我,还是要给初学者一个答复的,所以便去网上搜索了相关信息,才了解到有MuPDF开源库的存在。
    MuPDF是一种轻量级的PDF,XPS和电子书阅读器。由各种平台的软件库,命令行工具和查看器组成。支持许多文档格式,如PDF,XPS,OpenXPS,CBZ,EPUB和FictionBook 2。
    后来,自己就根据网上搜索到的一些资料,实现了基于MuPDF库将PDF指定页转换成PNG格式图片的小程序。现在,我就把程序的实现思路和过程写成文档,分享给大家。
    实现思路对于MuPDF库的源码下载以及编译过程,可以参考本站的《使用VS2013编译MuPDF库》这篇文章。
    其实,在MuPDF库中就提供了这一个功能:将PDF指定页转换成PNG格式图片,所以,我们直接编译MuPDF提供的代码就可以了。
    示例程序代码位于MuPDF库源码的“docs”目录下的“example.c”文件,我们只需使用VS2013创建一个空项目,然后把“example.c”文件导入项目中,接着将“include”目录中的头文件以及编译出来的libmupdf.lib、libmupdf-js-none.lib、libthirdparty.lib导入文件中即可。
    其中,“example.c”中主要的函数就是 render 函数,我们主要是把参数传进render函数,就可以把pdf转换成png图片了。
    对于“example.c”的代码可以不用修改,直接编译运行即可。但是,为了方便演示,我们还是对“example.c”中的 main 函数进行修改。
    编码实现以下是“example.c”文件中 render 函数的源码:
    void render(char *filename, int pagenumber, int zoom, int rotation){ // Create a context to hold the exception stack and various caches. fz_context *ctx = fz_new_context(NULL, NULL, FZ_STORE_UNLIMITED); // Open the PDF, XPS or CBZ document. fz_document *doc = fz_open_document(ctx, filename); // Retrieve the number of pages (not used in this example). int pagecount = fz_count_pages(doc); // Load the page we want. Page numbering starts from zero. fz_page *page = fz_load_page(doc, pagenumber - 1); // Calculate a transform to use when rendering. This transform // contains the scale and rotation. Convert zoom percentage to a // scaling factor. Without scaling the resolution is 72 dpi. fz_matrix transform; fz_rotate(&transform, rotation); fz_pre_scale(&transform, zoom / 100.0f, zoom / 100.0f); // Take the page bounds and transform them by the same matrix that // we will use to render the page. fz_rect bounds; fz_bound_page(doc, page, &bounds); fz_transform_rect(&bounds, &transform); // Create a blank pixmap to hold the result of rendering. The // pixmap bounds used here are the same as the transformed page // bounds, so it will contain the entire page. The page coordinate // space has the origin at the top left corner and the x axis // extends to the right and the y axis extends down. fz_irect bbox; fz_round_rect(&bbox, &bounds); fz_pixmap *pix = fz_new_pixmap_with_bbox(ctx, fz_device_rgb(ctx), &bbox); fz_clear_pixmap_with_value(ctx, pix, 0xff); // A page consists of a series of objects (text, line art, images, // gradients). These objects are passed to a device when the // interpreter runs the page. There are several devices, used for // different purposes: // // draw device -- renders objects to a target pixmap. // // text device -- extracts the text in reading order with styling // information. This text can be used to provide text search. // // list device -- records the graphic objects in a list that can // be played back through another device. This is useful if you // need to run the same page through multiple devices, without // the overhead of parsing the page each time. // Create a draw device with the pixmap as its target. // Run the page with the transform. fz_device *dev = fz_new_draw_device(ctx, pix); fz_run_page(doc, page, dev, &transform, NULL); fz_free_device(dev); // Save the pixmap to a file. fz_write_png(ctx, pix, "out.png", 0); // Clean up. fz_drop_pixmap(ctx, pix); fz_free_page(doc, page); fz_close_document(doc); fz_free_context(ctx);}
    程序测试我们修改 main 函数直接调用上述函数接口, main 函数为:
    int main(int argc, char **argv){ // 文件路径 char filename[MAX_PATH] = "C:\\Users\\DemonGan\\Desktop\\test.pdf"; // 转换的页码数 int pagenumber = 1; // 缩放比例 int zoom = 100; // 旋转角度 int rotation = 0; // 处理 render(filename, pagenumber, zoom, rotation); system("pause"); return 0;}
    直接运行程序,目录下有 out.png 图片生成,打开图片查看内容,发现是 test.pdf 的第一页内容,所以转换成功。
    总结这个程序的实现,自己可以不用写代码就可以完成。因为MuPDF已经把接口都封装好了,而且也有示例程序可以直接调用。如果想要把界面做得更有好些,可以把程序写成界面程序,然后直接调用现在的这个 render 函数接口,进行转换即可。
    3 留言 2018-11-06 22:31:04 奖励15点积分
  • 基于VS2012校友录管理系统

    一、设计任务分析1.1 问题描述定义通讯录类,属性有:编号、姓名、性别、出生年月、届数、学院、班级、电话、爱好、QQ、家庭地址等信息和相关的对属性做操作的行为。
    主要完成对校友通讯录的简单管理。
    1.2 功能要求
    添加功能:程序能够添加通讯录信息,要求编号要唯一,如果添加了重复编号的记录时,则提示数据添加重复并取消添加
    查询功能:可根据姓名、电话等信息对已添加的信息进行查询,如果未找到,给出相应的提示信息,如果找到,则显示相应的记录信息
    显示功能:可显示当前系统中所有通讯信息,每条记录占据一行
    编辑功能:可根据查询结果对相应的记录进行修改,修改时注意编号的唯一性
    删除功能:主要实现对已添加的通讯记录进行删除。如果当前系统中没有相应的人员记录,则提示“记录为空!”并返回操作
    保存功能:可将当前系统中通讯录记录存入文件中,存入方式任意
    读取功能:可将保存在文件中的信息读入到当前系统中,供用户进行使用

    二、模块设计
    录入纪录(录入成员:编号、姓名、性别、出生年月、届数、学院、班级、电话、爱好、QQ、家庭地址等信息)
    查询纪录(可根据姓名、电话、邮箱地址等信息对已添加的信息进行查询,如果未找到,给出相应的提示信息,如果找到,则显示相应的记录信息)
    修改纪录(对查出成员信息进行修改和保存)
    显示纪录(显示相对应成员的信息)
    删除纪录(主要实现对已添加的通讯记录进行删除。如果当前系统中没有相应的人员记录,则提示“记录为空!”并返回操作)
    清空纪录(清空对应信息)

    三、主要数据结构软件中使用的结构体和结构体数组,如:
    strcpy(st[i].name,name);strcpy(st[i].sex,sex);strcpy(st[i].birth,birth);strcpy(st[i].school,school);st[i].Class=Class;strcpy(st[i].tel,tel);strcpy(st[i].hobby,hobby);strcpy(st[i].qq,qq);strcpy(st[i].address,address);
    对用户的信息进行保存,他们都以二进制的形式读写。
    其中:
    cout<<"请输入要修改的学生的姓名:\t";char pName[20]; cin>>pName; for(i=0;i<num;i++)
    分别对姓名、性别、出生年月、届数、学院、班级、电话、爱好、QQ、家庭地址等进行储存。
    在子函数中运用了结构体数组,还运用了头文件:student.h。
    其中还使用大量的子函数,例如:
    void input(); //增加一条通讯录信息void search(); //查询指定同学的信息void modify(); //修改某一个同学的信息void display(); //输出全部信息void deletes(); //删除指定记录void qingkong(); //清空所有记录void menu(); //菜单选择void biaotou(); //表头
    共八个公有成员函数,九个私有成员。
    公有
    void input(); // 用于添加成员。void search(); // 用于查询存入的成员。void modify(); // 用于对已存入成员进行修改。void display(); // 用于将保存的信息进行输出。void deletes(); // 用于删除通讯录中已有的成员。void qingkong(); // 用于将通讯录中的所有成员清空。void menu(); // 主菜单,用于用户对功能的选择。void biaotou(); // 链表的标头。
    私有
    char name[20]; // 存放姓名 char sex[4]; // 存放性别char birth[10]; // 存放生日 char school[20]; // 存放学院int Class; // 存放班级 char tel[15]; // 存放电话号码char box[10]; // 存放邮政编码 char qq[15]; // 存放QQ号char address[20]; // 存放家庭地址
    四、流程图
    0 留言 2019-11-28 14:54:16 奖励20点积分
  • 《Windows黑客编程技术详解》勘误——13.3 文件管理之NTFS解析

    在内核篇第13章的第3小节“文件管理之NTFS解析”一文中,由于有网友测试反馈说该小结代码偶尔会出现定位不到存在的文件问题!经过我的排查,是由于配套代码中,多个Data Run的处理逻辑有问题!
    现在为了方便大家理解修改后的源代码,我在此补充下多个Data Run的处理说明。
    在NTFS文件系统中,如果数据很大的话,通常会使用Data Run来记录开辟的新空间,而且这些数据可能会不连续,所以就会出现多个Data Run的情况。
    Data Run的含义分析如下:

    Data Run的第一个字节分高4位和低4位。其中,高4位表示文件内容的起始簇号在Data Run List中占用的字节数。低4位表示文件内容簇数在Data Run List中占用的字节数。
    Data Run的第二个字节开始表示文件内容的簇数,接着表示文件内容的起始簇号。
    Data Run可以指示空间的大小以及偏移位置,例如上述中给的例子,起始簇号为:A2 59 00(10639616),数据大小为:C0 14(49172)。
    现在需要补充的知识点是针对多个Data Run的,如下所示!!!
    对于多个Data Run的情况,第一个Data Run的起始簇号是一个正整数,而第二个Data Run开始,起始簇号偏移是分正负的。可以根据起始簇号偏移的最高位是否来判断,若为1,则是负整数(补码表示);否则,是正整数。而且,从第二个Data Run开始,起始簇号偏移都是相对于上一个Data Run的起始簇号来说的。下面举个例子,方便大家理解。
    例如,有这么一个Data Run如下所示:
    31 01 FD 0A 28 21 01 AB FA 21 01 4A F5 21 01 91 C1 00我们可以看到上面一共有4个Data Run,分别如下:
    第1个Data Run

    31 01 FD 0A 28
    正整数:第一个Data Run的起始簇号都是正整数起始簇号:28 0A FD(2624253)

    第2个Data Run

    21 01 AB FA
    负整数:起始簇号偏移FA AB的最高位是1,所以是负整数(补码),所以FA AB(-1365)起始簇号:相对于上一个Data Run的偏移,所以为:2624253-1365=2622888

    第3个Data Run

    21 01 4A F5
    负整数:起始簇号偏移F5 4A的最高位是1,所以是负整数(补码),所以F5 4A(-2742)起始簇号:相对于上一个Data Run的偏移,所以为:2622888-2742=2620146

    第4个Data Run

    21 01 91 C1
    负整数:起始簇号偏移C1 91的最高位是1,所以是负整数(补码),所以C1 91(-15983)起始簇号:相对于上一个Data Run的偏移,所以为:2620146-15983=2604163

    配套代码修正对该小节配套代码中的HandleAttribute_A0函数和FileContentOffset函数的Data Run处理修改为上述补充的多个Data Run处理:
    计算Data Run的其实簇号代码修改为如下所示:
    if (0 == llClusterOffet){ // 第一个Data Run for (DWORD i = bHi; i > 0; i--) { liDataRunOffset.QuadPart = liDataRunOffset.QuadPart << 8; liDataRunOffset.QuadPart = liDataRunOffset.QuadPart | lpBuffer[wAttributeOffset + wIndxOffset + dwCount + bLo + i]; }}else{ // 第二个及多个Data Run // 判断正负 if (0 != (0x80 & lpBuffer[wAttributeOffset + wIndxOffset + dwCount + bLo + bHi])) { // 负整数 for (DWORD i = bHi; i > 0; i--) { // 补码的原码=反码+1 liDataRunOffset.QuadPart = liDataRunOffset.QuadPart << 8; liDataRunOffset.QuadPart = liDataRunOffset.QuadPart | (BYTE)(~lpBuffer[wAttributeOffset + wIndxOffset + dwCount + bLo + i]); } liDataRunOffset.QuadPart = liDataRunOffset.QuadPart + 1; liDataRunOffset.QuadPart = 0 - liDataRunOffset.QuadPart; } else { // 正整数 for (DWORD i = bHi; i > 0; i--) { liDataRunOffset.QuadPart = liDataRunOffset.QuadPart << 8; liDataRunOffset.QuadPart = liDataRunOffset.QuadPart | lpBuffer[wAttributeOffset + wIndxOffset + dwCount + bLo + i]; } }}// 注意加上上一个Data Run的逻辑簇号(第二个Data Run可能是正整数、也可能是负整数(补码表示), 可以根据最高位是否为1来判断, 若为1, 则是负整数, 否则是正整数)liDataRunOffset.QuadPart = llClusterOffet + liDataRunOffset.QuadPart;llClusterOffet = liDataRunOffset.QuadPart;
    修改后的源码,重新上传在下面的附件中了!!!若对书中内容有疑惑或者发现错误,可以直接戳下面的勘误收集链接哦
    https://www.write-bug.com/article/1966.html
    2 留言 2019-01-21 13:13:32
  • 基于Python实现的新闻网络爬虫程序 精华

    1、简介1.1 引用术语与缩写解释


    缩写、术语
    解 释




    Python
    一种简洁而强大的解释型脚本语言


    pyodbc
    Python下的ODBC数据库访问组件


    SQLAlchemy
    Python下的ORM数据访问组件


    pywin32
    Python下的Win32接口访问组件


    requests
    Python下的Web访问组件


    Pillow
    Python下的图像处理组件


    解释型语言
    无需编译源码可敏捷部署并执行的语言


    IOC
    控制反转,运行时可由数据反向干涉程序执行逻辑的设计模式


    RegularExpression
    正则表达式,查找和替换文本模式的简洁而灵活的表示法


    XML
    扩展标记语言,用于配置及数据交互



    1.2 概要本文档针对以下三个方面进行了详细说明:

    架构说明,对新闻网络爬虫的核心架构进行描述,供开发人员在开发或升级应用时参考
    部署说明,对新闻网络爬虫的部署步骤进行描述,供部署人员进行应用部署或升级时参考
    扩展说明,对新闻网络爬虫的扩展模式进行描述,供开发人员扩展爬虫功能时参考

    1.3 应用设计目标
    易于扩展
    易用
    易于维护

    为了达成这些设计目标我们采用了以下设计机制:

    易于扩展

    使用解释型、面向对象的Python语言实现程序逻辑:解释型语言易于扩展部署,结合抓取模块的IOC机制,更新升级时无需停机;面向对象易于代码复用,创建新爬虫模块时仅需少量重载代码修改控制流程IOC化,利用XML配置文件,可动态增减爬虫任务、控制爬虫任务启动条件、更改爬虫规则以及爬虫逻辑
    易用

    服务化,爬虫任务被封装为Windows 服务,可便捷地启动与停止,于后台执行长期运行
    易于维护

    编程语言简洁,缩进式风格减少了语法性代码,动态数据类型减少了声明性代码,函数式编程特性减少了重复性函数代码,友好而功能强大的标准库与外部组件也减少逻辑复杂性数据访问层模型化:使用SQLAlchemy组件,对数据访问进行对象化建模,屏蔽数据访问细节,如SQL,数据模型易于维护

    2、架构说明新闻网络爬虫程序主题为一系列python脚本,通过文件夹进行模块隔离,基于命名约定进行动态逻辑加载,根目录为ArticleSpider。
    整体框架如下:


    SpiderService.py:服务入口模块,用以处理Windows服务Article Spider Service的安装、卸载、启动、停止与重启
    SpiderTask.py:任务管理模块,负责加载控制规则配置、安排爬虫任务计划、组合爬虫任务子逻辑
    ArticleStorer.py:文章转存模块,包含数据库访问、图片转存与切图、队列消息发送功能
    RuleReader.py:规则读取模块,用于读取爬虫规则,辅助IOC机制
    Spider:爬虫逻辑模块,核心模块群,可根据需要添加新爬虫模板,爬虫模板可继承,基模块为Spider.py,多个相似爬虫可根据规则设置复用同一个爬虫模板
    Model:数据模型模块,维护爬虫相关ORM数据模型,由上下文管理层、数据模型层与事务逻辑层组成
    Message:消息处理模块,主要负责封装与发送队列消息
    SpiderRule.xml:爬虫规则配置,XML格式元数据
    Temp:缓存目录,用以缓存转存完成前的中间文件,如下载图片
    Log:日志目录,用以保存日志,采用循环日志模式
    ServiceCommand.txt:服务入口命令,用于参考的爬虫服务基本人机交互命令
    SpiderTest.py:爬虫测试模块,用于测试的相关脚本

    2.1 模块说明2.1.1 服务入口层 SpiderService.py2.1.1.1 SpiderService
    win32serviceutil.ServiceFramework:服务基类,引入自pywin32组件,用以将Python程序封装位Windows服务
    SvcDoRun:服务启动入口,重载基类方法,用以开启SpiderTask任务管理线程,阻塞等待任务结束事件
    SvcStop:服务终止入口,重载基类方法,用以终止SpiderTask任务管理线程,发起任务结束事件

    2.1.1.2 ServiceCommand
    python SpiderService.py install:爬虫服务安装,必须指令,用以注册Windows服务,安装成功后可直接于Windows服务管理器中进行服务启动、停止等管理操作
    python SpiderService.py install —startup auto:自启动爬虫服务安装
    python SpiderService.py start:启动爬虫服务,服务安装后有效
    python SpiderService.py restart:重启爬虫服务,服务启动后有效
    python SpiderService.py stop:停止爬虫服务,服务启动后有效
    python SpiderService.py remove:删除/卸载爬虫服务,服务安装后有效

    2.1.2 任务管理层 SpiderTask.py2.1.2.1 SpiderTask
    threading.Thread:线程管理基类,引入自python标准库,用以将主任务进行线程化封装
    __init__:初始化方法,进行一系列任务初始化操作,如线程初始化、日志初始化、调度初始化、存储初始化等操作
    ScheduleTask:任务调度方法,读取爬虫规则,根据设置生成爬虫调度计划
    RunSpiderByRule:爬虫驱动方法,按照给定规则,驱动对应爬虫开启任务,核心步骤为,爬虫选用—文章抓取—图片转存—文章入库—后续处理(如压图与消息通知)
    run:任务子线程启动入口,重载基类方法,以日为周期进行调度-执行-休眠循环,苏醒后调度爬虫任务—按照调度计划处理任务(执行或等待)—计划完成后休眠至下一周期苏醒
    StopTask:任务终止方法,当前任务完成后终止任务计划,非强行中断任务子线程,若要强行中断,可直接结束主线程,任务子线程将自动中断

    2.1.3 规则读取层 RuleReader.py2.1.3.1 RuleReader
    __init__:初始化方法,确定规则读取模式(目前仅支持XML模式),模式有效时进行初始规则读取
    FreshRules:规则刷新方法,读取最新规则,默认以XML格式读取规则,若要采用其他方法(如数据库读取、Json接口读取等),可继承该基类后进行重载扩展

    2.1.3.2 GetRuleFromNode功能性函数,从XML节点中提取爬虫规则字典,属性及简单子节点直接提取入本级字典,复杂子节点递归提取为子级字典后加入本级字典。
    2.1.3.3 PrintNode调试用函数,用于打印XML节点数据,采用前序遍历法。
    2.1.3.4 SpiderRule爬虫规则字典(dict类型),存储爬虫规则参数,以福州旅游网爬虫规则为例:

    name:规则名称,通常以爬取来源站命名,如福州旅游网
    sourceId: 来源标识,参照文章来源枚举值,默认为0
    rule:子级明细规则字典
    url:来源Url,明细规则,如:http://lyj.fuzhou.gov.cn/lyjzwgk/lydt/index.htm
    reAbstract:文章摘要正则表达式,明细规则,扫描文章清单时使用,如:
    <li>.+?<span>\[(.+?)\].+?href="(.+?)".+?>(.+?)</a>.+?</li>
    reArticle:文章正文正则表达式,明细规则,扫描文章正文时使用,如:
    <div class="content-boxtext">(.+?)</div>\s*<div class="content-boxbottom">
    reImage:文章图片正则表达式,明细规则,扫描文章图片时使用,如:
    <IMG.+?src="(.+?)".+?/>
    module:爬虫模块名,明细规则,反射加载的爬虫模块名与类名,如FuZhouSpider
    regionId:目的地标识,明细规则,爬虫目的地对应的乐途目的地字典标识值,如130
    spiderName:爬虫编辑ID,明细规则,利用爬虫对内发布文章的虚拟编辑用户名,如jishutest
    isValid:有效标志,明细规则,启用该规则时为1,停用为0,禁用为-1
    minPage:最小页码,明细规则,分页爬取时第一页的页码参数,默认为-1(不分页)
    maxPage:最大页码,明细规则,分页爬取时最后页的页码参数,默认为-1(不分页)
    wakeTime:子级苏醒时间字典,明细规则,可包含多个时间点
    timePotX:时间点,X代表时间点标识,多个时间点标识不可相同,时、分、秒必须

    2.1.4 文章转存层 ArticleStorer.py2.1.4.1 ArticleStorer文章转存器,组织下层通信、数据模块进行数据交互,如数据库访问、队列消息发送、文件上传、远程接口调用等。

    __init__:初始化方法,设定图片转存API(imageApi)、数据库连接(dbConStr)、消息队列(msmqPath)及压图API(picCutApi)等通信参数,并进行初始化
    DumpImage:图片转存方法,转存文章正文中已缓存的的下载图片至图片转存API,同时关联转存图片路径
    DumpArticle:文章入库方法,将已经完成图片转存并替换Url的文章正文按照正文页入库,同时记录爬取关联信息
    NewArticleCheck:新文章检查方法,比对爬取关联信息,确定是否为新文章或可更新文章
    SendSuccessMessage:后期处理消息信息发送方法,向消息队列发送信息,通知后续处理程序处理刚刚发布或变更的新正文页
    CutImaages:正文页压图方法,调用压图接口对指定正文页正文内的图片进行压图处理
    ArticleRepublish:正文页重发布方法,将正文页重新发布,即向消息队连续发送一条删除发布请求与新发布请求

    2.1.5 文章爬虫层 Spider该层为目录,包含多个XSpider.py模块,其中Spider.py为基础模块,其他模块如FuZhouSpider.py为基于Spider.py模块的扩展的模板化模块。
    2.1.5.1 Spider爬虫基类,封装爬虫所需的基本操作,可由子类继承复用(如规则加载、HTML下载、图片下载等共同操作)或重载(文章抓取、图片替换等区别操作)。

    ReadRule:规则加载方法,可继承复用,可嵌套重载,加载爬虫规则字典内参数信息,并对爬虫伪装(Http Header)、数据缓存、数据清理(Css清理、Herf清理等)等参数进行设置
    CatchArticles:文章抓取方法,不可直接复用,必须替换重载,为各爬虫模板的独有逻辑,目前主要有页面抓取及异步JS抓取两种基本模式
    DownLoadHtml:Html源码下载方法,可继承复用,可重载(如进行拟人化访问伪装) ,用于获取抓取页面的相关Html源码,如文章列表页、文章正文页以及异步加载API
    DownLoadImage:图片下载方法,可继承复用,可重载(如应对图片防盗链机制),用于下载文章正文中的图片链接至缓存路径,同时进行图片格式标准化
    ReplaceArticleImages:缓存文章正文图片替换方法,可继承复用,可重载(如对转存图片标签添加属性),用于将抓取文章正文中原来的图片链接替换为转存后的图片链接
    CacheArticle:文章信息缓存方法,可继承复用,可重载(如定制文章特有属性信息),用于组装文章属性信息并加入缓存等待下一步处理
    ClearArticles:文章正文清洗方法,可继承复用,可重载(如添加JS清晰),用于清洗文章正文中的无效信息,如当前已支持的CSS、Herf、空白字符及样式清洗
    ClearTempImages:缓存图片清理方法,可继承复用,可重载(如添加缓存备份机制),用于清理缓存的已下载图片

    2.1.5.2 Functions文章爬虫层中的相关功能性函数。

    ReplaceImage:图像Url替换函数,可将文章正文中的正则匹配项(原始图片链接)替换为转存图片链接
    ClearExternalCss:CSS清理函数,可将文章正文中的正则匹配项(Css类)清空
    ClearExternalHerf:Herf清理函数,可将文章正文中的正则匹配项(超链接)去链接处理
    ClearExternalBlank:空白字符清理函数,可将文章正文中的正则匹配项(空白字符)去除
    ClearExternalStyle:样式清理函数,可将文章正文中的正则匹配项(style样式)去除
    ComposeUrl:相对Url组装函数,将页面中的相对Url结合页面Url组装为绝对Url
    ConvertImageToJpg:图片格式标准化函数,将不同格式的图片(如PNG、BMP)同意转化为JPG格式

    2.1.5.3 XSpider各种爬虫模板类,通常直接继承自Spider,但也可继承其他XSpider,区别主要在于CatchArticles方法的重载实现,目前主要分为两种模式。

    页面爬虫:CatchArticles方法直接解析页面源码,根据制定的编码格式,提取文章关键信息,由扫描列表页的文章摘要、扫描正文页的正文、扫描正文域的图片三步组成,典型模板如FuZhouSpider。
    Json Api爬虫:CatchArticles方法获取Json API响应,将结果反序列化为字典对象,提取文章关键信息,由提取列表API的文章摘要、提取正文API的正文与图片两步组成。,典型模板如FuZhouTourSpider。

    其他类型的模板可根据实际情况自行扩展,如XML API、SOAP API等。
    2.1.6 数据模型层 Model该层为目录,包含多个SpiderContext.py、SpiderEntity.py与SpiderData.py三个模块。

    SpiderContext.py:数据上下文模块,负责数据库连接、数据模型初始化、会话管理
    SpiderEntity.py:数据模型模块,负责数据实体对象模型映射,即表与类的映射
    SpiderData.py:数据逻辑模块,负责组织会话内的数据访问逻辑,也是对外接口

    2.1.6.1 SpiderDataHelper爬虫数据访问类,属于数据逻辑模块。

    SaveArticle:文章入库方法,将爬去并完成过滤的文章信息写入数据库,若是新文章,文章信息入库同时写入文章导入信息,若不是新文章(导入信息已存在),仅进行修改
    NewArticleCheck:新文章检查方法,用于防止文章重复导入,对比文章导入信息,若不匹配(根据文章Url与发布日期),则认定为新文章,否则认定为重复文章
    GetArticlesLikeDampSquibs:未成功发布文章扫面方法,用于查询出已发布但未成功的文章,返回必要属性信息,如正文页ID,目的地ID等

    2.1.6.2 ModelMapper实体关系映射(类表映射)函数,属于数据模型模块,将指定的实体类与数据表绑定,当前已映射对如下:

    Cms_Information—Cms_Information:正文页信息类与正文页信息表
    Cms_Information_Inported—Cms_Information_Inported:正文页导入信息类与正文页导入信息表
    Cms_InformationRegion—Cms_InformationRegion:正文页目的地关联类与正文页目的地关联表

    2.1.6.3 ModelContext数据模型上下文类,属于数据上下文模块,管理数据连接上下文。

    __init__:上下文初始化方法,注册数据库连接、初始化数据模型元数据并建立会话生成器
    Session:会话入口,申请一个数据库会话

    2.1.6.4 Session_Scope数据会话域函数,属于数据上下文模块,负责隔离会话事务的生命周期,具有contextmanager特性,即以迭代生成器的方式隔离会话事务逻辑无关的细节,确保成功自动提交、失败自动回滚并且结束自动释放。
    2.1.7 消息模型层 Message该层为目录,包含SpiderMessageQueue.py模块,负责格式化消息,并与消息队列通信。
    2.1.7.1 Message消息类,负责消息格式化,将消息转化为制定输出格式。

    __init__:初始化方法,将消息字典初始化为XML结构,同时添加必要属性
    ToFormatString:序列化方法,将XML结构的消息转化为字符串,同时添加必要说明性内容

    2.1.7.2 ToXmlElement功能性函数,将字典转化为XML结点格式,简单键值对直接转化为子节点,复杂键值对进行嵌套转化,值数组转化为多个子节点,值为字典则进行递归转化。
    2.1.7.3 MessageQueue消息队列访问类,封装消息队列接口。

    __init__:初始化方法,组装MSMQ队列基本信息,包含主机名与队列路径
    SendMessage:消息发送方法,根据给定MSMQ队列基本信息,创建会话并发送消息

    2.1.7.4 Queue_Scope队列会话域函数,负责隔离队列会话的生命周期,具有contextmanager特性,即以迭代生成器的方式隔离队列会话逻辑无关的细节,确保会话结束自动释放。
    3、部署说明3.1 运行环境
    PC配置

    2G以上内存
    操作系统

    Windows XP Professional 2002 Service Pack 3+
    相关工具

    Python 3.3 Pyodbc 3.0.7 SQLAlchemy 0.9.7pywin32 219requests 2.4.1Miscrosoft Message QueueMiscrosoft SQL Server 2005SQL Server Native Client 10.0

    3.2 资源目录
    源码路径,192.168.80.157主机 E:\shuaxin\ArticleSpider。
    工具路径:192.168.80.157主机 E:\tools\爬虫项目部署包

    3.3 部署步骤3.3.1 Python确认部署主机python-3.x版本已安装,建议使用python-3.3稳定版本。
    若未安装,执行爬虫项目部署包内python-3.3.5.1395976247.msi文件,以安装python虚拟机,安装目录可设置为E:\tools\Python33。
    确保系统环境路径(高级系统设置-环境变量)含有python安装目录,确保路径内可引用python.exe。
    3.3.2 MSMQ确保Miscrosoft Message Queue已开启,且存在消息队列路径\private$\queuepathnews,且该队列对EveryOne开放消息发送权限,若该队列不存在,则依赖部署条件不满足(后续处理程序未部署),不可进行应用部署。
    3.3.3 DataSource Driver确保主机ODBC数据源中已安装SQL Server Native Client 10.0驱动程序(版本可以更高,但ArticleStorer.py中dbConStr也应对应修改)已正确安装。
    若未安装,根据系统环境,选择执行爬虫项目部署包内sqlncli_X86.msi或sqlncli_X64.msi,以安装数据源驱动。
    3.3.4 Pyodbc确保python安装目录(如E:\tools\Python33)下,存在以下相对路径的目录Lib\site-packages\pyodbc…,即Pyodbc已安装。
    若未安装,根据系统环境,执行爬虫项目部署包内pyodbc-3.0.7.win32-py3.3.exe,以安装Python ODBC组件。
    3.3.5 Pywin32确保python安装目录(如E:\tools\Python33)下,存在以下相对路径的目录Lib\site-packages\pythonwin,即Pywin32已安装。
    若未安装,根据系统环境,执行爬虫项目部署包内pywin32-219.win32-py3.3.exe,以安装Python Win32组件。
    3.3.6 Pillow确保python安装目录(如E:\tools\Python33)下,存在以下相对路径的目录Lib\site-packages\PIL,即Pillow已安装。
    若未安装,根据系统环境,执行爬虫项目部署包内Pillow-2.6.0.win32-py3.3.exe,以安装Python Image Library组件。
    3.3.7 Requests确保python安装目录(如E:\tools\Python33)下,存在以下相对路径的目录Lib\site-packages\requests,即Requests已安装。
    若未安装,启动cmd,切换至爬虫项目部署包下requests-2.4.1目录,执行命令python setup.py install,以安装Python Requests组件。
    3.3.8 SQLAlchemy确保python安装目录(如E:\tools\Python33)下,存在以下相对路径的目录Lib\site-packages\sqlalchemy,即SQLAlchemy已安装。
    若未安装,启动cmd,切换至爬虫项目部署包下SQLAlchemy-0.9.7目录,执行命令python setup.py install,以安装Python SQLAlchemy组件。
    3.3.9 SQL Server确保81主机上的lotour库,已存在Cms_Information_Inported表。
    若不存在,执行初始化脚本:
    CREATE TABLE [dbo].[Cms_Information_Inported] ( [SourceUrl] VARCHAR (256) NOT NULL, [Status] SMALLINT NOT NULL, [InformationId] INT NOT NULL, [SourceTime] DATETIME NOT NULL, [RegionId] INT NOT NULL, [SourceName] VARCHAR(50) NULL, PRIMARY KEY CLUSTERED ([SourceUrl] ASC));CREATE NONCLUSTERED INDEX [IX_Cms_Information_Inported_InformationId]ON [dbo].[Cms_Information_Inported]([InformationId] ASC);
    3.3.10 Spider Service检查服务管理程序中,是否存在Article Spider Service服务,即爬虫服务是否已安装。若未安装,选定部署路径,将ArticleSpider目录移动至对应路径,启动cmd,切换至部署目录,执行命令python SpiderService.py install,以安装爬虫服务。
    应用扩展升级时无须重新安装,除非变更SpiderTask.py,亦无须停止Article Spider Service服务,直接添加或替换对应源文件即可(次日生效),但重启服务可确保变更立即生效。
    进入服务管理程序,启动Article Spider Service,若启动成功,整个部署流程完成。
    3.4 配置参数3.4.1 图片转存APIArticleStorer.py中的imageApi参数,指向CMS正文页的图片上传接口,默认为’http:// localhost:8037/WS/newsUploadImage.ashx’。
    3.4.2 数据库连接ArticleStorer.py中的dbConStr参数,对应Lotour库的数据连接字符串,默认为:
    mssql+pyodbc:// testUser:test@localhost:1433/news?driver=SQL Server Native Client 10.0其中mssql表示DBMS为Microsoft Sql Server,pyodbc表示驱动模式为Python ODBC,testUser为数据库用户名,test为用户密码,@至?间区域表示数据库路径,?后区域表示数据库驱动名称。
    3.4.3 消息队列ArticleStorer.py中的msmqPath参数,指向CMS正文页的发布消息队列,默认为:\PRIVATE$\queuepathnews。
    3.4.4 压图APIArticleStorer.py中的picCutApi参数,指向CMS正文页的压图接口,默认为: http://cms.lotour.com:8343/WS/newsCutImage.ashx 。
    3.4.5 图片缓存路径Spider.py中的temp参数,指向文章爬取过程中下载中间图片的缓存目录,默认为相对路径’\temp\‘。
    3.4.6 日志设置SpiderTask.py中的logging.basicConfig函数的相关入口参数,作为爬虫日志设置,可参考python官方文档中的logging部分,当前使用循环日志,部分参数如下:

    filename,日志文件名,默认使用相对路径’\Log\spider.log’
    mode,文件使用模式,默认使用添加模式,即’a’
    maxBytes,循环日志块大小,默认为2M
    backupCount,循环日志块数量,默认为8
    encoding,日志编码,默认为’utf-8’
    format,日志信息格式,用于将Trace信息与Message信息格式化,默认为:
    %(asctime)s %(levelname)-10s[%(filename)s:%(lineno)d(%(funcName)s)] %(message)slevel,日志记录级别,默认为DEBUG,线上部署推荐使用WARN或ERROR

    3.4.7 爬虫规则路径SpiderTask.py中的rulePath参数,默认为XML文件路径,默认值为相对路径’\SpiderRule.xml’。
    4、扩展说明4.1 扩展范围网络爬虫服务扩展分为三个级别,分别为规则扩展、模板扩展以及功能扩展,以应对不同的扩展需求。

    规则扩展:改动规则文件,即SpiderRule.xml,用于爬虫规则微调,以及添加仅需复用爬虫模板的新爬虫(如同站新频道,或同网站架构)
    模板扩展:新增爬虫实现,即添加XSpider.py,继承Spider基类,重载实现特有逻辑(如文章抓取逻辑),必要时可单独添加独立功能,如防盗链破解功能
    功能扩展:变更或重组爬虫功能,任何文件都可能改动,请在理解架构的前提下进行,如将规则元数据由XML元数据变更为可维护更大规模元数据的数据库元数据表,或者将爬虫服务重组以添加文章智能过滤层

    4.2 扩展示例4.2.1 规则扩展规则扩展仅需修改SpiderRule.xml文件,以福州旅游网爬虫为例:
    <rule name="福州旅游网" sourceId="0"><url>http://lyj.fuzhou.gov.cn/lyjzwgk/lydt/index.htm</url><reAbstract><li>.+?<span>\[(.+?)\].+?href="(.+?)".+?>(.+?)</a>.+?</li></reAbstract> <reArticle><div class="content-boxtext">(.+?)</div>\s*<div class="content-boxbottom"></reArticle> <reImage><IMG.+?src="(.+?)".+?/></reImage> <module>FuZhouSpider</module> <regionId>130</regionId> <spiderName></spiderName> <isValid>1</isValid> <minPage>-1</minPage> <maxPage>-1</maxPage> <wakeTime> <timePot0>08:00:00</timePot0> <timePot1>13:00:00</timePot1> </wakeTime></rule>
    一个Rule节点便是一项爬虫规则,结点属性及子节点含义参照3.1.3.4。
    每增加一个爬虫任务,则需要添加对应的Rule节点,添加流程如下:
    4.2.1.1 确定文章来源
    name:根据抓取需求,将name设置为[站名][-频道名]
    sourceId:进入CMS媒体管理模块,检查媒体来源,若不存在现有对应媒体来源,进行添加操作,将sourceId设置为对应媒体来源ID,若不指定媒体来源,忽略sourceId属性或置0
    url:观察抓取url对应源代码,若页面含有文章列表,即可按照网页抓取模式处理,url可直接设置为页面url;若列表内容为异步加载(可参考福州旅游资讯网),此时应分析源代码,找到对应的文章列表API,将url设置为对应API;若url中含有动态参数或分页参数,可使用{n}参数顺序替代(从{0}开始),在抓取逻辑中填入参数
    minPage & maxPage:若需求抓取页面不只一页,则将minPage置为起始页,maxPage置为终止页,同时定制扩展功能,利用参数化的url在抓取逻辑中进行处理
    regionId:抓取源应指定目的地,通常以乐途目的地字典中的ID值配置regionId即可,若有特殊情况(如混合目的地抓取),应在抓取逻辑中提取指定

    4.2.1.2 确定正则表达式
    reAbstract:若文章摘要为页面抓取模式,应分析页面源码,抽象出提取文章摘要信息的正则表达式,其中文章链接必须提取,文章标题与发布时间则是能提取尽量提取,将正则表达式进行html转义处理后置入reAbstract(参考在线转义工具如http://www.cnblogs.com/relucent/p/3314831.html )。若文章摘要为异步加载模式,reAbstract可按照抓取逻辑定制填写,正则表达式不必须,可空置或置为其他数据,如参数化文章正文API
    reArticle:若文章正文为页面抓取模式,应分析页面源码,抽象出提取文章正文信息的正则表达式,其中文章正文必须提取,文章标题与发布时间若在摘要中未提取也必须提取(无法提取时,应在抓取逻辑中另行处理)。若文章正文为异步加载模式,reArticle可按照抓取逻辑定制填写,正则表达式不必须,可空置或置为其他数据,如参数化图片API
    reImage:若图片隐藏在文章正文中,应分析正文代码,抽象出提取图片的正则表达式,图片链接必须提取。若图片信息独立于正文,reImage可按照抓取逻辑定制填写,正则表达式不必须,可空置或置为其他数据,如防盗链破解参数。

    4.2.1.3 确定爬虫模板module:不同规则的爬虫抓取逻辑可能各不相同,也可能存在复用,因此可在规则中指定爬虫模板,若无可复用模板,也可创建新爬虫模板,然后将module置为模板名称;模板名称格式通常为XSpider,不同模板名称不可重复,模板定义可参考模板扩展示例。
    4.2.1.4 确定任务计划
    isValid:爬虫规则可自由预设,但只有将isValid被置为1的规则,在爬虫任务计划扫描时进入任务队列,isValid置为0时表示停用,置为-1时表示禁用,其他取值可在功能扩展中确定
    spiderName:爬虫抓取文章后会发布正文页,默认会以匿名的乐途小编身份发表,此时spiderName置空,若要指定发布人,应将spiderName置为特定编辑者的CMS用户名
    wakeTime:爬虫任务若要执行,至少还需要一个执行时间,爬虫将在指定时间之后被唤醒,执行任务,此时应在wakeTime中添加timePotX子节点,X用于区别多个不同时间点(如0、1、2),意味着统一爬虫可在一日之内的不同时间启动多次

    4.2.2 模板扩展模板扩展需添加XSpider.py文件,在其中实现继承自Spider或其他模板Spider的XSpider类,其中CatchArticles必须重载以实现定制化的抓取逻辑,以福州福州旅游网爬虫为例:
    class FuZhouSpider(Spider.Spider): """福州旅游网 Spider""" def __init__(self): Spider.Spider.__init__(self) def CatchArticles(self): recAbstract = re.compile(self.reAbstract, re.DOTALL) recArticle = re.compile(self.reArticle, re.DOTALL) recImage = re.compile(self.reImage, re.DOTALL) html = self.DownLoadHtml(self.url, '文章列表页{0}访问失败,异常信息为:{1}') if html == None: return self.articles for x in recAbstract.findall(html): article = dict( time = datetime.datetime.strptime(x[0],'%Y-%m-%d'), # url = self.url[0:self.url.rfind('/')] + x[1][1:], url = Spider.ComposeUrl(self.url, x[1]), title = x[2] ) html = self.DownLoadHtml(article['url'], '文章页{0}访问失败,异常信息为:{1}') if html == None: continue content = None images = [] imageCount = 0 for y in recArticle.findall(html): content = y for z in recImage.findall(content): imageCount += 1 # imageUrl = article['url'][0:article['url'].rfind('/')] + z[1:] imageUrl = Spider.ComposeUrl(article['url'], z) image = self.DownLoadImage(imageUrl, '图片{0}提取失败,异常信息为:{1}') if image == None: continue images.append(image) if not content \ or imageCount != len(images): continue self.CacheArticle(article, content, images, '成功自{0}提取文章') return self.articles
    由于采用继承机制,__init__方法中应调用父级__init__方法,CatchArticles方法则可利用基类模板中的方法、字段及函数,结合自有方法、字段及函数扩展抓取逻辑,扩展流程如下:
    4.2.2.1 处理继承要素选定基类
    通常使用Spider.Spider,其中前一个Spider为模块名,对应Spider.py,后一个Spider为类名,如需更细粒度的模板复用,如定制页面抓取模板或异步抓取模板,可继承对应二级模板。
    选定重载方法
    继承模板通常会复用基类模板的大部分方法,但作为区别,必定有自身的特定逻辑,比如重载CatchArticles方法用以实现不同爬虫抓取逻辑(该方法在Spider中为虚方法,继承Spider后必须实现);
    除CatchArticles方法外,其他基类方法也存在扩展空间,可参考3.1.5.1中的方法说明进行重载。
    添加自有元素
    通常情况下,基类元素已经足够使用,但在一些特殊场景,可能需要添加一些新元素,比如文章过滤函数,图片抗反盗链下载之类的功能型函数,然后在重载的方法中使用。
    4.2.2.2 组织抓取逻辑抓取源加载
    根据爬虫规则中定义的url或者Api,从抓取源下载数据;
    页面抓取时,需指定指定抓取页面的编码格式,默认为utf-8编码,但部分页面会使用gbk或者gb2312编码;
    异步抓取或多页抓取时,API通常需要一些需要二次指定的参数,此时应将参数赋值再发起请求;
    抓取源通依照抓取模式存在区别,页面抓取源通常由三段式的文章列表页、文章正文页与文章正文组成,异步抓取源则随API的定义不同而不同,混合抓取则结合了前两者。
    基类抓取相关方法包括DownLoadHtml与DownLoadImage,DownLoadHtml用于加载Html源码或者Api响应数据,DownLoadImage则用于下载图片等文件至缓存路径。
    数据解析与提取
    抓取数据最终需转化为文章基本信息,用于后期处理,其中必要组成部分包含文章摘要信息(文章源链接、标题及发布日期等)、文章正文以及图片清单(图片链接及缓存路径等);
    页面抓取数据需要使用正则表达式解析,对应正则表达式由基类从爬虫规则读取,由reAbstract(文章摘要规则)、reArticle(文章正文规则)以及reImage(图片规则)组成,分别可提取信息至article(文章基本信息)、content(文章正文)以及images(图片列表);
    异步抓取通常不需要使用正则表达式,因为抓取数据通常为结构化数据,可以直接反序列化为友好的数据结构,如Json数据,可先进行清洗(将null替换为Python中的None),然后直接使用eval函数转化为数据字典;
    reAbstract、reArticle及reImage在异步抓取或混合抓取中的存储涵义开放,可配合模板自行定义,如API,筛选规则等,只需要保证最终可正确提取文章基本信息;
    提取文章发布时间时,应注意时间数据的表示形式,指定转化格式(如%Y-%m-%d可匹配2014-10-1),并注意从摘要数据和从正文数据提取的区别;
    正则解析函数主要使用findall进行全文搜索,注意该函数匹配结果为多项时将返回匹配结果迭代器,为单项时将直接返回匹配结果;
    信息提取完成后,应调用基类的CacheArticle方法,将article、content与images组装并缓存,等待后起批量处理。
    4.2.2.3 文章后期处理通常情况下图像后期处理对模板扩展透明,但其中的部分环节可进行独立定制,文章后期处理的流程为:
    有效性检查—图片转存—文章正文图片链接替换—正文清洗—文章入库—文章图片压图—发布。
    其中模板扩展可参与环节主要为文章正文图片链接替换(ReplaceArticleImages)与正文清洗(ClearArticles)环节。
    ReplaceArticleImages扩展示例如更改img标签的替换形式,如加入alt等属性。
    ClearArticles扩展示例如增加过滤规则,如JS过滤。
    4.2.3 功能扩展功能扩展自由度较大,但通常情况下是对爬虫服务任务流程进行重组。
    爬虫服务主流程为:
    扫描爬虫任务—执行爬虫任务—休眠,其功能扩展主要集中在任务管理层与规则读取层。
    特定规则的执行流程为:
    加载任务—文章抓取—有效性检查—图片转存—文章正文图片链接替换—正文清洗—文章入库—文章图片压图—发布,其功能扩展主要集中在文章转存层、数据模型层以及消息模型层。
    功能扩展的方式主要有两种,加入中间层以添加中间步骤以及重载替换特定步骤,对应的可扩展空间请参阅架构说明部分。
    2 留言 2019-05-02 11:18:20 奖励25点积分
  • 基于JAVA的简易计算器

    一、任务目标
    学会分析“简易计算器”任务的实现思路
    根据思路独立完成“简易计算器”的源代码编写、编译和运行
    掌握正则表达式来判定数字键或者数据是否合法
    掌握String类常用方法的使用,如:contains方法等
    掌握Java异常处理机制
    熟练掌握Swing包(JTextField控件、JButton控件和控件数组)的使用,以及常用布局方式的使用
    掌握GUI开发过程中如何处理组件上发生的界面事件

    二、实现思路2.1 界面布局实现思路
    根据实验要求,利用GridBagLayout布局将每个组件放在合适的位置,利用GridBagConstraints类中的Insets方法实现组件间隔
    利用数组存放每个组件显示的文本

    2.2 事件处理实现思路设计ComputerListener接口继承按钮触发事件ActionListener接口以增加其抽象方法实现将界面事件传至PoliceListen类(PoliceListen类实现接口ComputerListener)做事件处理。
    2.3 计算功能实现思路
    输入合法机制

    避免第一位为符号,设置判断当第一位按非数字使不处理当第一位为零,第二位也为零,设置判断当第一位为零时输入数字无效避免首位为零,其后出现多个零(即0001),判断该输入的倒数第二位是否为符号,倒数第一位是否为0,在对按钮0。是则不做处理避免输出数字不合法(多个小数点 即6.6.6),利用循环以符号位为分割线,判该数字是否存在已存在小数点,即每个运算符号后的数字至多存在一个小数点排除多符号一起串连(即8+*9+6*/5),点击运算符触发事件并判断前一位是否为符号,是则不做处理
    计算字符串

    判断最后一位是否为运算符,利用String类中的charAt()方法提取最后一位进行判断,是则提示错误,否则运算字符和数字分离,两次利用StringTokenizer类进行字符串提取分析分别得到数字序列和运算符序列将数字序列和运算符序列存放在两个链表中,链表的删除较为便利根据优先级计算,设置两个循环(当符号链表中的数据不为空则继续),第一个循环计算所有得乘除,即符号前后得两个数乘除,结果放在第一个数中删除第一个数和删除符号;第二个循环计算所有的加减,结果放在第一个数中删除第一个数和删除符号

    三、实现代码及运行结果UML图

    实现代码

    运行结果

    四、总结或感悟4.1 错误分析
    输入一个数中多个小数点
    分析器的StringTokenizer的是否错误
    正则表达式的使用错误

    4.2 错误解决
    以运算符为分割,运算符后的数字之多只有一个小数点
    在分析器使用分析字符串中的标点符号时未排除”0”,最后在单步调试中发现错误
    “[+-*/]” 该正则表达式可以匹配”.”,导致浪费大量的时间检查在”.”的触发事件上

    4.3 总结感悟通过本次实验学习到了以下几点:

    该开始入手实验时无从下手,一边考虑如何布局,一边考虑如何输出合法,一边考虑如何实现计算,一心三用,没有主次先后的编程观念。浪费比较多的时间;而后有了步骤,先将组件间的布局做好,在考虑输入数据的合法,最后在合法的情况下实现计算
    学习到了一种新的布局方式GridBagLayout布局,该布局也是一种类似网格布局,改进GridLayout布局不能改变组件在网格中的大小的,组件间的间隔问题
    本次实验未使用 WindowBuilding插件,主要的原因时想检验以下自己在暑假看课本的成果,以及个人认为在使用插件时容易将代码弄乱。不过在布局方面花费比较多的时间。但也熟悉了几个布局的特点,以及他们的常用方法
    本次实验最大的收获就是熟悉掌握了一个类中实例的对象在利用构造方 法在各个类中的重复使用,以及复习接口的相关知识

    五、代码附录Text.java

    ComputerListener.java

    Win.java

    PoliceListen.java
    1 留言 2019-11-17 20:15:59 奖励15点积分
  • 大数据 13、推荐算法-CF

    前文链接:https://write-bug.com/article/2376.html
    推荐算法在大数据Hadoop平台2-2、MapReduce和上节的实践中都提到了检索或推荐系统的数据来源的底层来源,那么这里即是召回阶段的算法。算法目的是侧重召回候选集合,粗排。
    我们在看爱奇艺网站时,上面会有一些推荐的热榜栏目和个性化推荐栏目等,那么这里的基础算法是什么样的呢?
    推荐算法简单分为两种:基于内容和协同过滤(行为)推荐
    1.1 基于内容Content base1.1.1 引入Item属性的Content Based推荐
    在前面介绍过的实践中,我们利用token实现item的倒排,其实就类似找到了一些item和item之间的相关性信息。我们做的正排表的分词就是此图内容分析索引,而相关性计算就是倒排索引,把它保存在nosql后就可以直接查询了。

    通常在网站主页中,不会做大面积的个性化推荐,其透漏着隐私问题会给用户带来困扰。
    1.1.2 引入User属性的Content Based推荐

    这里我们需要把item的token改成可以描述我用户行为偏好的token。
    个性化:

    Step1:user搜索、点击、收藏过这些item时(也就是用户行为数据),可以用这些item的token 给 (用户分析) user做用户画像(兴趣建模)
    Step2:—->倒排索引—-》token—-》索引—->item
    Step3:相关计算cos、jaccard ∩/∪等
    Step4:排序

    1.2 基于协同Collaboration Filtering核心:User对Item的打分形成UI稀疏矩阵(即只存储有行为的数据)。
    存储可以类比原来的倒排索引:userid(key)—->(value)item item item
    原来的倒排:一个token被几个item同时包含。
    这里的倒排:一个用户对几个item有过行为,即被一个用户作用过的几个item有着相关性。

    那稀疏矩阵是什么样子呢?

    数据例:
    1,100001,5 1,100002,3 1,100003,4 1,100004,3 1,100005,3 1,100007,4 1,100008,1 1,100009,5 1,1000011,2这个稀疏矩阵就是UI矩阵,只存储用户有行为的数据,由此数据量可以大大减小:即倒排索引。
    随着时间的推移,日志服务器会积累越来越多的行为数据形成UI矩阵。通常中型以上的互联网公司,用户量最少一亿以上(一个用户多个账号),音乐量级几十万以上,所以这个UI矩阵很大很大,需要用稀疏矩阵存储。

    由这个UI矩阵我们知道,userA对ItemBCD有过行为,那么由前面倒排索引的知识,我们可以得到itemA被哪些用户有过行为,得到了userlist,那我把ItemB为key就得到了UserA的好友列表。例:微博
    根据user A的好友列表userBCD,可以得到每个user背后的item列表,把这些item聚合推荐给userA。例:个性化推荐
    根据itemA背后的user列表做相似度关联,把每个用户背后的item聚合,在和itemA相关联。例:非个性化推荐


    可由1我们得到user和user的矩阵即UU=UI*IU(UI的转置矩阵);可由3我们得到item和item的矩阵即II=IU*UI。由此我们引出了两种协同:User-Base CF和Item-Base CF。
    1.2.1 user-Base —- CF:—————外交假设:
    – 用户喜欢那些跟他有相似爱好的用户喜欢的东西
    – 具有相似兴趣的用户在未来也具有相似兴趣
    方法:
    – 给定用户u,找到一个用户的集合N(u),他们和u具有相似的兴趣
    – 将N(u)喜欢的物品推荐给用户.

    UI矩阵计算——————》 UU矩阵—————-用户之间相似度
    流程:转置,归一,cos
    目的:如果把一个user没看过的电影推荐给他,打一个分预测用户,对这个电影有多大的兴趣

    r(userid,itemid)=两个用户的相似度×另一个用户的打分 / 所有用户(随机选择一部分)相似度加和这个结果,只需要基于同一套算法(UU / II)的排序,不需要具体分数。
    1.2.2 item -Base —- CF:———自身历史行为假设:
    – 用户喜欢跟他过去喜欢的物品相似的物品
    – 历史上相似的物品在未来也相似
    方法:
    – 给定用户u,找到他过去喜欢的物品的集合R(u)
    – 把和R(u)相似的物品推荐给u
    UI矩阵计算——————》 II矩阵—————电影之间相似度
    流程:转置,归一,cos
    目的:如果把一个user没看过的电影推荐给他,打一个分预测用户,对这个电影有多大的兴趣

    r(userid,itemid)=两个item的相似度×该用户的打分 / 所有item(随机选择一部分)相似度加和什么时候用哪个算法?

    性能角度:谁维度小用谁
    领域:UB时效性(看是否火,用户之间相互影响),IB倾向于个性化(历史相似物品)
    实时性:IB更高:新行为导致推荐结果实时变化
    冷启动:UB需要时间离线计算,IB围绕产生新行为的物品无限推荐
    推荐理由:UB难解释

    冷启动三类:—————-新东西——粗粒度吸引

    用户:推荐热门排行榜、注册年龄性别等做画像粗粒度推荐、社交网站导入好友信息推荐、新登陆反馈标签推荐
    物品:新物品通过基于内容推荐——-CB
    系统:不太好解释的,用户画像(职业)和物品(不懂这东西)———引入专家知识(知识图谱,人工校验等)建立相关物品矩阵

    I I/UU 相似度计算公式:———————cos

    类比cos=向量内积 / 向量模 ------把两个劈开相乘
    ——————> 归一化 ui / ui^2———————->两个Rui 评分相乘
    比如上面的两个电影 M 和 T 的相似度是0.57
    U(i,j)=A B D
    R(ua,M)=5 , R(ua,T)=1R(ub,M)=1 , R(ub,T)=5R(ud,M)=4 , R(ud,T)=3归一化:
    25+1+16=42---根号42M:5/1/4/1+25+9=35------根号35T:1/5/3/M*T=实践:协同过滤————————给定一个item,推荐相关的item集合(II矩阵)
    倒排式、分块式
    倒排式流程:
    遍历排列组合同一个人观看的所有item,得到很多相似度的pair;如果两个物品被越多的人贡献过,相似度越准确,去除偶然性误点。也就是排列出所有组合pair对,-----有几个相同对,相当于被几个人贡献过。相同的pairkey会分在同一个桶里,计算相似度分数就会越准确---套公式(reduce中)

    Step1:归一UI矩阵:

    Map:
    矩阵输入数据:U,I,S输出:Key(i),Value(u,s)对
    Reduce:
    输入:Key(i),Value(u,s)对输出:Key(u),Value(i,s_new)对

    Step2:衍生 I I Pair对:

    Map:
    输入:Key(u),Value(i,s_new)对输出:Key(u),Value(i,s_new)对
    Reduce:
    输入:Key(u),Value list((i,s_new))对输出:Key_1(i) , Value_1(j,s_new_is_new_j)Key_2(i) , Value_2(i,s_new_is_new_j)

    Step3:result :

    Map:
    输入: Key(i) , Value(j,s_new_i*s_new_j)输出:Key<i,j> , Value(s_new_i*s_new_j)
    Reduce:
    输入:Key<i,j> , Value list((s_new_i*s_new_j))输出:Key(i) , Value(j,score)


    前面说了一些最基础的推荐算法,但是其中存在了一些问题,我们会在后面更新基础的聚类算法中指出。并且在其中我们使用倒排式实现了协同过滤,突出了MR批量计算和对大规模UI矩阵的批量处理,但在其中代码的缺点也很明显,我们输入的UI矩阵产生pair对时,会产生比UI大得多的数据,并会有一些重复数据,很容易爆掉内存,所以我们需要优化在UI矩阵中取物品需要对每一个用户的物品候选集合设置阈值,也就是pair对多少的阈值,如果说一个用户点击了几乎所有item,pair数据指数级增长,内存就会立刻爆掉。后面:聚类、MF(ALS、LFM、SVD、SVD++)、ANN
    3 留言 2019-04-25 11:23:42 奖励20点积分
  • python数据分析(4)——数据预处理(上)

    数据预处理的主要内容包括数据清洗、数据集成、数据变换和数据规约。

    1 数据清洗数据清洗主要是删除原始数据集中的无关数据、重复数据,平滑噪声数据,筛选掉与挖掘主题无关的数据,处理缺失值、异常值。
    1.1 缺失值处理方法主要分为删除记录、数据插补和不处理,其中常用的数据插补方法如下。

    这里主要介绍拉格朗日插值法和牛顿插值法。其他的插值方法还有Hermite插值、分段插值和样条插值。


    P(x)是牛顿插值逼近函数,R(x)是误差函数。
    3)将缺失的函数值对应的点x代入插值多项式得到缺失值的近似值f(x).
    牛顿插值法也是多项式插值,但采用了另一种构造插值多项式的方法,与拉格朗日插值相比,具有承袭型和易于变动节点的特点。从本质上来说,两者给出的结果是一样的(相同次数、相同系数的多项式),只不过表示的形式不同。因此,在Python的Scipy库中,只提供了拉格朗日插值法的函数(因为实现上比较容易),如果需要牛顿插值法,则需要自行编写函数。
    例子是catering_sale.xls
    #拉格朗日插值代码import pandas as pd #导入数据分析库Pandasfrom scipy.interpolate import lagrange #导入拉格朗日插值函数inputfile = 'catering_sale.xls' #销量数据路径outputfile = 'sales.xls' #输出数据路径data = pd.read_excel(inputfile) #读入数据data[u'销量'][(data[u'销量'] < 400) | (data[u'销量'] > 5000)] = None #过滤异常值,将其变为空值#自定义列向量插值函数#s为列向量,n为被插值的位置,k为取前后的数据个数,默认为5def ployinterp_column(s, n, k=5): y = s[list(range(n-k, n)) + list(range(n+1, n+1+k))] #取数 y = y[y.notnull()] #剔除空值 return lagrange(y.index, list(y))(n) #插值并返回插值结果#逐个元素判断是否需要插值for i in data.columns: for j in range(len(data)): if (data[i].isnull())[j]: #如果为空即插值。 data[i][j] = ployinterp_column(data[i], j)data.to_excel(outputfile) #输出结果,写入文件
    1.2 异常值处理
    我们一般将异常值视为缺失值进行插补。
    python中判断每个元素是否空值/非空值

    D.isnull/notnull()

    2. 数据集成2.1 实体识别实体识别是指从不同数据源识别出现实世界的实体,它的任务是统一不同源数据的矛盾之处。
    2.1.1 同名异义数据源中同样是属性ID,不一定是同一实体。
    2.1.2 异名同义不同数据源中不同名字的数据项表示是同一实体。
    2.1.3 单位不统一描述同一个实体分别用的是国际单位和中国传统的计量单位。
    检测和解决这些冲突就是实体识别的任务。
    2.2 冗余属性识别
    同一属性多次出现同一属性命名不一致导致重复
    有些冗余属性可以用相关分析检测。
    python去除数据中的重复元素

    D.unique()
    np.unique(D)
    2 留言 2018-12-25 14:24:18 奖励15点积分
  • 把修改/更新过的项目重新提交至github上

    更新项目提交至github只需要几条命令即可:

    在本地的git仓库将你修改过的项目复制到下面(覆盖掉之前上传的)
    右击选择Git Bash Here打开命令行
    输入下面四行命令即可

    git statusgit add . (别忘了add后面+空格+.)git commit -m “备注”git push origin master

    最后刷新下就OK了。
    1 留言 2019-05-13 16:34:48 奖励2点积分
显示 0 到 15 ,共 15 条
eject