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

  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>

整个 \<span> \</span> 是元素, \<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
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
    Q1
    Q2
    Q2

2.好像是运行了脚本

Q3
Q3

据查将“script”修改成别的字符便不会运行脚本,并没尝试成功。

效果

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

sqlite
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

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

To be continued…