MFC网页分析程序




  
  
  开发一个网页分析程序,可以抓取特定网页的内容,加以分析之后将结果保存至数据库。

  V1.5上线,保存至sqlite数据库用时2分13秒。         

网页分析程序具体要求描述如下:
1. 使用http技术获取一个博客的首页http://blog.csdn.net/jiangsheng
2. 分析这个网页的内容,从中找到博客中每一篇文章的链接。
3. 通过这些链接,获取文章的正文网页,从内容中提取文章的标题和文章的内容。
4. 将文章的标题与内容分别保存至数据库。
5. 布局要求:提供一个列表框和一个多行文本框。列表框中显示从数据库中获取的文章标题列表;当点击列表框中的某一篇文章时,在文本框中显示该文章的内容。

获取源码

因为需多次调用获取源码功能,将它放入一个函数中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
CString CGetWebDlg::DownloadCodes(CString path)
{
CInternetSession session;
CHttpFile *file = NULL;
CString strURL = path;
CString strHtml = _T(""); //存放网页数据
try
{
file = (CHttpFile*)session.OpenURL(strURL);
}
catch (CInternetException *m_pException)
{
file = NULL;
m_pException->m_dwError;
m_pException->Delete();
session.Close();
MessageBox("网络连接错误!", "提示");
}
CString strLine;
char sRecived[1024];
if (file != NULL)
{
while (file->ReadString((LPTSTR)sRecived, 1024) != NULL)
{
strHtml += sRecived;
}
}
else
{
AfxMessageBox(_T("失败!"));
}
session.Close();
file->Close();
delete file;
file = NULL;

strHtml = ConvertUtf8ToGBK(strHtml);//源码转换

return strHtml;
}

编码问题

注意网页编码问题,因此需要格式转换,编写一个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void  ConvertUtf8ToGBK(CString &strUtf8)
{

int len=MultiByteToWideChar(CP_UTF8, 0, (LPCTSTR)strUtf8, -1, NULL,0);
unsigned short * wszGBK = new unsigned short[len+1];
memset(wszGBK, 0, len * 2 + 2);
MultiByteToWideChar(CP_UTF8, 0, (LPCTSTR)strUtf8, -1, (LPWSTR)wszGBK, len);

len = WideCharToMultiByte(CP_ACP, 0, (LPCWSTR)wszGBK, -1, NULL, 0, NULL, NULL);
char *szGBK=new char[len + 1];
memset(szGBK, 0, len + 1);
WideCharToMultiByte (CP_ACP, 0, (LPCWSTR)wszGBK, -1, szGBK, len, NULL,NULL);

strUtf8 = szGBK;
delete[] szGBK;
delete[] wszGBK;
}

显示源码

有些类似网页爬虫的感觉。

由于我刚开始是用VC6.0创建项目,现在用VS2013打开,因此,提示报错:
error MSB8031: Building an MFC project for a non-Unicode character set is deprecated. You must change the project property to Unicode or download an additional library. See http://go.microsoft.com/fwlink/p/?LinkId=286820 for more information.

解决:用于多字节字符编码 (MBCS) 的 MFC 库 (DLL) 不再包含于 Visual Studio 中,但是可用作插件,可以在任何装有 Visual Studio Professional、Visual Studio Premium 或 Visual Studio Ultimate 的计算机上下载和安装。下载地址:https://www.microsoft.com/zh-cn/download/details.aspx?id=40770

解析

为了接下来的操作,我去学正则表达式了……
还是学习html解析库 htmlcxx
官方
HtmlCxx用户手册

HTMLCXX

下载htmlcxx库

http://sourceforge.net/projects/htmlcxx/
并解压。

编译

打开htmlcxx.vcproj,右键属性,配置属性-C/C++-代码生成-运行库:多线程调试 DLL (/ MDd)进行编译。编译会报错,将

1
const char *signature = "";

改为

1
const char *signature = "\xEF\xBB\xBF";

即可编译成功。

导入

把生成的htmlcxx.lib和html文件夹拷贝到所需的工程中。即:
在所开发项目文件夹中,新建”htmlcxx“文件,里面添加两个子文件夹”lib“和”include“。将编译好的htmlcxx.lib拷贝到lib文件夹,将html文件夹中所有的.h头文件和ParserSax.tcc添加到include文件夹。添加库文件htmlcxx.lib到项目中,具体说来:

在VS工程中,添加c/c++工程中外部头文件及库的基本步骤:
1、添加工程的头文件目录:工程—属性—配置属性—c/c++—常规—附加包含目录:加上头文件存放目录。
2、添加文件引用的lib静态库路径:工程—属性—配置属性—链接器—常规—附加库目录:加上lib文件存放目录。
然后添加工程引用的lib文件名:工程—属性—配置属性—链接器—输入—附加依赖项:加上lib文件名。
3、添加工程引用的dll动态库:把引用的dll放到工程的可执行文件所在的目录下。
注意:第一步可以不用,直接在工程里加入动态库的头文件,在使用代码处引用这个头文件。

所开发的项目的头文件中添加以下内容:

1
2
3
4
5
6
7
#include <string>
#include "htmlcxx/include/ParserDom.h"

using namespace std;
using namespace htmlcxx;

#pragma comment(lib,"htmlcxx.lib")

测试

官方测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <htmlcxx/html/ParserDom.h>
...

//Parse some html code
string html = "<html><body>hey</body></html>";
HTML::ParserDom parser;
tree<HTML::Node> dom = parser.parseTree(html);

//Print whole DOM tree
cout << dom << endl;

//Dump all links in the tree
tree<HTML::Node>::iterator it = dom.begin();
tree<HTML::Node>::iterator end = dom.end();
for (; it != end; ++it)
{
if (it->tagName() == "A")
{
it->parseAttributes();
cout << it->attributes("href");
}
}

//Dump all text of the document
it = dom.begin();
end = dom.end();
for (; it != end; ++it)
{
if ((!it->isTag()) && (!it->isComment()))
{
cout << it->text();
}
}

然而……

报错1

加入#include”iostream”头文件即可。

报错2

修改为:

1
2
3
4
5
6
if (it->tagName() == "A")
{
it->parseAttributes();
std::pair<bool, std::string> pa = it->attribute("href");
cout << pa.second;
}

编译通过。

还有其他的库也可以用,比如使用MSHTML解析HTML页面
比如LIBXML2库使用指南
还可以用正则表达式写库……

突然发现 原来的计划里有COM组件 XML和HTML 数据库访问技术
都没怎么接触过 补补补
在填坑的路上不能止步…

参考

C++ 使用Htmlcxx解析Html内容(VS编译库文件)
html与xml解析库htmlcxx使用过程中的若干问题及解决方案
c++ hmtlcxx 学习之旅

MSHTML

因为最近用过MSXML,就试试MSHTML。学有余力的话,htmlcxx之后还是想玩一下…
https://msdn.microsoft.com/en-us/library/aa741317(v=vs.85).aspx
蒋晟-关于MSHTML
https://msdn.microsoft.com/zh-cn/library/mshtml(v=vs.110).aspx

MSHTML导入

系统中自带了mshtml,和msxml一样,在C盘windows/system32中可找到。

如何导入

先看MSDN……
MSDN-MSHTML

再看各种搜集的文章
http://bbs.csdn.net/topics/330214041
http://www.cnblogs.com/speedmancs/archive/2010/08/11/1797442.html
http://blog.csdn.net/jinyaba/article/details/17097323
https://social.msdn.microsoft.com/Search/zh-CN?query=MSHTML&pgArea=header&emptyWatermark=true&ac=4#refinementChanges=117&pageNumber=1&showMore=false
https://wenku.baidu.com/view/d571abc4ec3a87c24028c4bb.html
http://www.codeguru.com/cpp/i-n/ieprogram/article.php/c4385/Lightweight-HTML-Parsing-Using-MSHTML.htm
http://www.yesky.com/403/1938403.shtml?qq-pf-to=pcqq.c2c
http://www.bianceng.cn/Programming/vc/201411/46771.htm
https://wenku.baidu.com/view/299bba4a336c1eb91a375df5.html

思路:下载源码和获取链接是两个独立函数,会被多次调用。先获取首页源码,div id=”archive_list” 遍历该div获取各月份归档链接,再使用多线程(48个线程???)进入每个归档链接里下载源码,获取源码中h1的每篇文章标题,保存到数据库。

虽然有两种方法,一种通过归档获得链接,一种通过翻页获得链接,但根据本html特点,明显通过翻页要简洁方便一些,因为翻页的链接是有规律的,可通过循环搞定。每页5篇直接获得正文链接,比从归档获得少一层。两核4个逻辑处理器,所以是开2个线程好还是4个好呢……

获取每篇正文链接,下载源码,解析得正文,保存到数据库。最后从数据库中提取标题和正文显示到对应窗口(使用ADO)。

解析过程

创建

1.使用CoCreateInstance创建一个接口

1
HRESULT hr = CoCreateInstance(CLSID_HTMLDocument, NULL, CLSCTX_INPROC_SERVER, IID_IHTMLDocument2, (void**)&pDoc);

2.创建一个COM中的数组,将HTML字符串写到数组中

a)SafeArrayCreateVector:函数用来创建一个对应的数组结构。函数有三个参数,第一个参数表示数组中元素类型,一般给VT_VARIANT表示它是一个自动类型,第二个参数数组元素起始位置的下标,对于VC来说,数组元素总是从0开始,所以这个位置一般给0,第三个参数是数组的维数,在这将它作为一个字符数组,所以是一个一维数组。
b)SafeArrayAccessData:允许用户操作这个数组,在需要读写这个数组时都需要调用这个函数,以便获取这个数组的操作权。它有两个参数,第一个参数是数组变量,第二个参数是一个输出参数,当调用这个函数成功,会提供一个缓冲区,操作这个缓冲区就相当于操作了这个数组。
c)SafeArrayUnaccessData:每当操作数组完成时需要调用这个函数,函数与SafeArrayAccessData配套使用,用来回收这个权限,并使对数组的操作生效。

  1. 调用接口的write方法,将接口与HTML字符串绑定
    1
    2
    3
    4
    5
    6
    7
    8
    SAFEARRAY* psa = SafeArrayCreateVector(VT_VARIANT, 0, 1);
    VARIANT *param;
    bstr_t bsData = (LPCTSTR)strHtml;
    hr = SafeArrayAccessData(psa, (LPVOID*)&param);
    param->vt = VT_BSTR;
    param->bstrVal = (BSTR)bsData;
    hr = pDoc->write(psa);
    hr = SafeArrayUnaccessData(psa);

目标:

1
2
3
4
<span class="link_title"><a href="/jiangsheng/article/details/9870241">
选择剪贴板格式顺序
</a>
</span>

整个 \ \ 是元素, \ 是标签,class是属性名,link_title是属性值,“选择剪贴板格式顺序”是文本。

元素遍历

至少两种方法:
法一:
获取了HTML文档的IID_IHTMLDocument2接口后,开始遍历:
1.get_all方法获取所有标签节点,这个函数通过一个输出参数输出IHTMLElementCollection类型的接口指针
2.用IHTMLElementCollection接口的get_length方法获取标签的总数量,据此写一个循环,在循环进行元素的遍历
3.循环中用IHTMLElementCollection接口的item方法进行迭代,获取各元素对应的IDispatch接口指针
4.调用IDispatch接口指针的QueryInterface方法生成对应的IHTMLElement接口。通过这个接口获取元素的各种信息

以下已能成功获取标题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
void CGetWebDlg::EnumElements(IHTMLDocument2* pDoc)
{
CComPtr<IHTMLElementCollection> pCollection;
pDoc->get_all(&pCollection);
if (NULL == pCollection)
{
return;
}
VARIANT varName;
CString strText;
long len = 0;
pCollection->get_length(&len);
for (int i = 0; i < len; i++)
{
varName.vt = VT_I4;
varName.lVal = i;
CComPtr<IHTMLElement> pElement;
CComPtr<IDispatch> pDisp;
pCollection->item(varName, varName, &pDisp);
if (NULL == pDisp)
{
continue;
}

pDisp->QueryInterface(IID_IHTMLElement, (LPVOID*)&pElement);
if (NULL != pElement)
{
BSTR bstrClass;
pElement->get_className(&bstrClass);
CString strClass = _com_util::ConvertBSTRToString(bstrClass);
if (strClass.Compare("link_title") == 0)
{
BSTR bstrText = NULL;
pElement->get_innerText(&bstrText);
strText = bstrText;
m_list.InsertItem(i, strText);
}
}
}
}

法二:
利用IHTMLDocument2将字符串形式的HTML转换为DOM对象,利用IHTMLDocument3的getElementByTagName等方法来操作DOM对象。
以下已能成功获取标题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
MSHTML::IHTMLDocument3Ptr pDoc3;
MSHTML::IHTMLElementCollectionPtr pCollection;
MSHTML::IHTMLElementPtr pElement;

pDoc3 = pDoc;
pCollection = pDoc3->getElementsByTagName(L"span");
for (long i = 0; i < pCollection->length; i++)
{
pElement = pCollection->item(i, (long)0);
if (pElement != NULL)
{
BSTR bstrClass;
pElement->get_className(&bstrClass);
CString strClass = _com_util::ConvertBSTRToString(bstrClass);
if (strClass.Compare("link_title") == 0)
{
BSTR bstrText = NULL;
pElement->get_innerText(&bstrText);
CString strText = bstrText;
m_list.InsertItem(i, strText);
}
}
}

其实两种方法大同小异,相较而言可能数据量大的话,法二效率高些吧。因为法一是直接遍历所有的元素寻找class相同的,而法二是先定位span,然后在span中找寻class。(getElementsByTagName只有IHTMLDocument3Ptr)

使用MSHTML解析HTML页面
变体VARIANT
MSDN-DOM https://msdn.microsoft.com/en-us/library/ms766487(v=vs.85).aspx
使用MSHTML接口获取链接

晚上几个小时做完了一半,抵了之前一两个月。
数据库这块没来得及做,没加多线程,很多细节还得调。但比起之前心有余而力不足的感觉还是好多了。 学过msxml后,学习mshtml确实强一点,比一个月前完全不知道怎么下手好很多了。

今晚总算做出来个半成品
半成品

正文解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
void CGetWebDlg::ArticleParse(CString strArticle, Blog* blog, int iRow)
{
IHTMLDocument2* pDoc;
MSHTML::IHTMLDocument3Ptr pDoc3;
MSHTML::IHTMLElementCollectionPtr pCollection;
MSHTML::IHTMLElementPtr pElement;

HRESULT hr = CoCreateInstance(CLSID_HTMLDocument, NULL, CLSCTX_INPROC_SERVER, IID_IHTMLDocument2, (void**)&pDoc);


//将代码放入安全数组并写入文档
SAFEARRAY* psa = SafeArrayCreateVector(VT_VARIANT, 0, 1);
if (psa == NULL || pDoc == NULL)
{
MessageBox(_T("创建Document2对象失败!"));
}
VARIANT *param;
bstr_t bsData = (LPCTSTR)strArticle;
hr = SafeArrayAccessData(psa, (LPVOID*)&param);
param->vt = VT_BSTR;
param->bstrVal = (BSTR)bsData;
hr = pDoc->write(psa);
//hr = pDoc->close();

//SafeArrayDestroy(psa);
hr = SafeArrayUnaccessData(psa);
_bstr_t href;
pDoc3 = pDoc;
pCollection = pDoc3->getElementsByTagName(L"div");
for (long k = 0; k < pCollection->length; k++)
{
pElement = pCollection->item(k, (long)0);
if (pElement != NULL)
{
BSTR bstrClass;
pElement->get_id(&bstrClass);
CString strClass = _com_util::ConvertBSTRToString(bstrClass);
if (strClass.CompareNoCase("article_content") == 0)
{
BSTR bstrText = NULL;
pElement->get_innerText(&bstrText);
CString strText = bstrText;
blog->article = strText;
m_list.SetItemData(iRow, (DWORD_PTR)blog);
}
}
}
}

建立了一个结构体用来存放每篇文章的标题和正文,方便点击列表控件项时读取对应的结构体。

1
2
3
4
5
struct Blog
{
CString title;
CString article;
};

列表控件和编辑框交互

列表控件初始化
1
2
3
4
CRect rectLocal;
m_list.GetClientRect(rectLocal); //获得当前客户区信息
m_list.InsertColumn(0, "序号", LVCFMT_LEFT, rectLocal.Width() / 6, 0);
m_list.InsertColumn(1, "文章列表", LVCFMT_LEFT, rectLocal.Width() / 6 * 5, 1);

添加多列后,单击只能选中第一列,这时需要修改风格 LVS_EX_FULLROWSELECT 表示整行。

1
m_list.SetExtendedStyle(LVS_EX_FULLROWSELECT);

添加数据

单纯添加一项的话: m_list.InsertItem(项的索引, 数据);
但要指定列的话:
m_list.InsertItem(项的索引, “”);
m_list.SetItemText(行, 列, 数据);

OnLvnItemchangedList

试了多种方法,只有这一种成功了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void CGetWebDlg::OnLvnItemchangedList1(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
// TODO: 在此添加控件通知处理程序代码
// m_edit.SetWindowText("");
// int index = m_list.GetNextItem(-1, LVNI_ALL | LVNI_SELECTED);
// Blog *pStructure = (Blog*)m_list.GetItemData(index);
// m_edit.SetWindowText(pStructure->article);
if (pNMLV->uNewState == (LVIS_SELECTED | LVIS_FOCUSED))
{
m_edit.SetWindowText("");
Blog *pStructure = (Blog*)m_list.GetItemData(pNMLV->iItem);
m_edit.SetWindowText(pStructure->article);
}
*pResult = 0;
}

实现列表和编辑框交互后,可以说这已经是一个可以完整运行的程序了,V1.0版本出炉。
实测解析时间:1分41秒
能够成功获取该博客111篇文章,能够正确显示正文(包括清晰显示代码)。现在的运行图:
V1.0
该版本未添加多线程、未和数据库关联。

V2.0实现数据库操作

使用sqlite数据库

下载导入

官网下载sqlite
Source Code sqlite-amalgamation-3200000.zip 有三个东西 shell.c sqlite3.c sqlite3.h
根据VS2010下SQLite3生成lib库文件 文章方法生成sqlite3.lib

Precompiled Binaries for Windows sqlite-dll-win32-x86-3200000.zip 有一个所需 sqlite3.dll

将lib和dll放入项目工程里,在.cpp开头加上

1
2
#include "sqlite3.h"
#pragma comment(lib,"sqlite3.lib")

保存至sqlite数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
int CGetWebDlg::Database(std::vector<Blog*> &vecBlog)
{
sqlite3 * pDB;
char* errMsg;
// 连接SQLite数据库
int res = sqlite3_open("test.db", &pDB);
if (res != SQLITE_OK)
{
MessageBox(_T("数据库打开失败,请检查后再操作!"), NULL, MB_ICONSTOP);
sqlite3_close(pDB);
return -1;
}

// 创建表
string strSQL = "create table blog (title text, article text);";
res = sqlite3_exec(pDB, strSQL.c_str(), 0, 0, &errMsg);
if (res != SQLITE_OK)
{
MessageBox(_T("数据库打开失败,请检查后再操作!"), NULL, MB_ICONSTOP);
//return -1;
}

// 插入数据
vector<Blog*>::iterator iter;
for (iter = vecBlog.begin(); iter != vecBlog.end(); iter++)
{

CString cstrTitle = (*iter)->title;
CString cstrArticle = (*iter)->article;
cstrTitle = ConvertGBKToUtf8(cstrTitle);
cstrArticle = ConvertGBKToUtf8(cstrArticle);

char *p1 = cstrTitle.GetBuffer(cstrTitle.GetLength() + 1);
cstrTitle.ReleaseBuffer();
char *p2 = cstrArticle.GetBuffer(cstrArticle.GetLength() + 1);
cstrArticle.ReleaseBuffer();
char *strSQL = sqlite3_mprintf("INSERT INTO blog VALUES('%q','%q')", p1, p2);
sqlite3_exec(pDB, strSQL, 0, 0, &errMsg);
}
}

当然,也可以事先把数据库和表创建好……
创建数据库和表

因为sqlite数据库是UTF-8格式存储,于是,需要一个转换函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CString  ConvertUtf8ToGBK(CString &strUtf8)
{

int len=MultiByteToWideChar(CP_UTF8, 0, (LPCTSTR)strUtf8, -1, NULL,0);
unsigned short * wszGBK = new unsigned short[len+1];
memset(wszGBK, 0, len * 2 + 2);
MultiByteToWideChar(CP_UTF8, 0, (LPCTSTR)strUtf8, -1, (LPWSTR)wszGBK, len);

len = WideCharToMultiByte(CP_ACP, 0, (LPCWSTR)wszGBK, -1, NULL, 0, NULL, NULL);
char *szGBK=new char[len + 1];
memset(szGBK, 0, len + 1);
WideCharToMultiByte (CP_ACP, 0, (LPCWSTR)wszGBK, -1, szGBK, len, NULL,NULL);

strUtf8 = szGBK;
delete[] szGBK;
delete[] wszGBK;
return strUtf8;
}

出现的问题

  1. 如图,数据少时并不会出现。
    Q1
    Q2

2.好像是运行了脚本
Q3
据查将“script”修改成别的字符便不会运行脚本,并没尝试成功。

效果

下载了一个SQLiteStudio可视化管理工具。可见数据已成功保存至数据库。
sqlite

从sqlite数据库读取数据

新建工程。
主要用到该函数:

1
2
3
4
5
6
7
8
9
int sqlite3_get_table(
sqlite3 *db, /* An open database */
const char *zSql, /* SQL to be evaluated */
char ***pazResult, /* Results of the query */
int *pnRow, /* Number of result rows written here */
int *pnColumn, /* Number of result columns written here */
char **pzErrmsg /* Error msg written here */
);
void sqlite3_free_table(char **result);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
void CGetDataFromSqliteDlg::OnBnClickedBtnreaddata()
{
// TODO: 在此添加控件通知处理程序代码
sqlite3 * pDB;
char* errMsg;
// 连接SQLite数据库
int res = sqlite3_open("test.db", &pDB);
if (res != SQLITE_OK){
MessageBox(_T("数据库打开失败,请检查后再操作!"), NULL, MB_ICONSTOP);
sqlite3_close(pDB);
}

string strSql = "select * from blog";
char** pResult;
int nRow;
int nCol;
int nResult;
nResult = sqlite3_get_table(pDB, strSql.c_str(), &pResult, &nRow, &nCol, &errMsg);
if (nResult != SQLITE_OK)
{
sqlite3_close(pDB);
cout << errMsg << endl;
sqlite3_free(errMsg);
}

std::vector<Blog*>vecBlog;

string strOut;
int nIndex = 2;
for (int i = 0; i < nRow; i++)
{
Blog* blog = new Blog;
string tempTitle = pResult[nIndex];
string tempArticle = pResult[nIndex + 1];
CString number;
number.Format("%d", i + 1);

m_list.InsertItem(nRow, "");
CString strTitle(tempTitle.c_str());
CString strArticle(tempArticle.c_str());
ConvertUtf8ToGBK(strTitle);
ConvertUtf8ToGBK(strArticle);
blog->title = strTitle;
blog->article = strArticle;
vecBlog.push_back(blog);
m_list.SetItemText(i, 0, number);
m_list.SetItemText(i, 1, strTitle);
m_list.SetItemData(i, (DWORD_PTR)blog);

nIndex = nIndex + 2;
}
sqlite3_free_table(pResult);
sqlite3_close(pDB);
}

同样地,注意格式转码。

成品

V1.5

V1.5成功实现从sqlite数据库读取数据并反映在列表和编辑框中。

To be continued…

文章目录
  1. 1. 获取源码
    1. 1.1. 编码问题
  2. 2. 解析
    1. 2.1. HTMLCXX
      1. 2.1.1. 下载htmlcxx库
      2. 2.1.2. 编译
      3. 2.1.3. 导入
      4. 2.1.4. 测试
      5. 2.1.5. 参考
    2. 2.2. MSHTML
      1. 2.2.1. MSHTML导入
      2. 2.2.2. 解析过程
        1. 2.2.2.1. 创建
        2. 2.2.2.2. 元素遍历
        3. 2.2.2.3. 正文解析
        4. 2.2.2.4. 列表控件和编辑框交互
          1. 2.2.2.4.1. 列表控件初始化
          2. 2.2.2.4.2. 添加数据
          3. 2.2.2.4.3. OnLvnItemchangedList
  3. 3. 使用sqlite数据库
    1. 3.1. 下载导入
    2. 3.2. 保存至sqlite数据库
    3. 3.3. 出现的问题
    4. 3.4. 效果
    5. 3.5. 从sqlite数据库读取数据
    6. 3.6. 成品
|