注册 登录
编程论坛 VFP论坛

再次求助:如何将《仙道九绝》各章节内容完整地爬下来?

王咸美 发布于 2025-12-05 21:34, 780 次点击
下列代码能将《仙道九绝》小说各章节的当前页爬下来,但各章节小说有的有三页,有的有两页,怎样完善下列代码将各章节小说完整的爬下来?请高手赐教,万分感谢!!!(不喜勿喷,全当路过)

SET DEFAULT TO (ADDBS(JUSTPATH(SYS(16))))
DECLARE long PostMessageA IN user32 long,long,long,long
IF !FILE("仙道九绝.dbf")
    CREATE TABLE 仙道九绝 (title C(100), addr C(254), txt M)
    USE
ENDIF
USE 仙道九绝 ALIAS tu
of = CREATEOBJECT("form1")
of.show(1)
CLOSE TABLES ALL
CLEAR ALL
RETURN

DEFINE CLASS form1 as Form
    width = 800
    height = 600
    AutoCenter = .T.
    AllowOutput = .f.
    ADD OBJECT but as commandbutton WITH left=10,top=10,width=100,height=22,caption="开始"
    ADD OBJECT grd as grid WITH left=10,top=40,width=250,height=550,RecordSource="tu",AllowCellSelection=.f.
    ADD OBJECT edt as editbox WITH left=280,top=40,width=510,height=550
    ADD OBJECT web as Olecontrol with OleClass="Shell.Explorer.2",left=-100

    PROCEDURE Destroy
        UNBINDEVENTS(this.hWnd)
    ENDPROC
   
    PROCEDURE Init
        this.web.Silent = .t.
        BINDEVENT(this.hWnd, 0x401, this, "myMessage")
    ENDPROC

    PROCEDURE but.click
        ZAP IN "tu"
        thisform.edt.value = " "
        thisform.web.navigate("http://www.)
    ENDPROC
   
    PROCEDURE grd.click
        thisform.edt.value = tu.txt
        thisform.edt.SelStart = 0
    ENDPROC

    PROCEDURE web.documentComplete(pdisp, url)
        IF (SYS(3095, pdisp) == SYS(3095, this))
            PostMessageA(thisform.hWnd, 0x401, 0, 0)
        ENDIF
    ENDPROC

    FUNCTION myMessage(hWnd, uMsg, wParam, lParam)
        dom = this.web.document
        IF VARTYPE(dom) != "O"
            this.Enabled = .t.
            RETURN
        ENDIF
        IF EMPTY(tu.addr)
        
            * ie版本低试用
            lis = dom.getElementsByTagName("li")
           FOR EACH li IN lis   
              IF li.classname == "line3"
                    INSERT INTO tu VALUES (;
                        li.getElementsByTagName("a").item[0].innertext,;
                        li.getElementsByTagName("a").item[0].href, "";
                  )
                ENDIF
            ENDFOR
         
            GO TOP IN "tu"
            this.grd.setfocus
            this.web.navigate(ALLTRIM(tu.addr))
            RETURN     
        ENDIF
        REPLACE tu.txt WITH ALLTRIM(tu.title)+ 0h0D0A0D0A + dom.getElementById("chapter").innertext
        ? ALLTRIM(tu.title)
        SKIP IN "tu"
        IF !EOF("tu")
            this.grd.setfocus
            this.web.navigate(ALLTRIM(tu.addr))
            RETURN
        ENDIF
        GO TOP IN "tu"
        this.grd.setfocus
        this.Enabled = .t.
    ENDFUNC
ENDDEFINE

[此贴子已经被作者于2025-12-5 21:35编辑过]

52 回复
#2
吹水佬2025-12-05 22:39
大家帮找找,哪章节有三页、有两页。
#3
王咸美2025-12-05 23:17
第1章3页,第2章2页
#4
吹水佬2025-12-05 23:20
我这看不到有翻页
截个图看看

#5
sam_jiang2025-12-06 00:17
win7+vfp9 下编译通过。。。

程序代码:

LOCAL loHtml, lcUrl, lcContent
DECLARE integer Sleep IN WIN32API integer

lcUrl = "http://www./6780946/3664234966.html"
loHtml = CREATEOBJECT("htmlfile")

* 打开HTML文件对象
loHtml.open()

* 创建XMLHTTP请求对象
*loXmlHttp = loHtml.parentwindow.XMLHTTP
loXmlHttp=CREATEOBJECT("WinHttp.WinHttpRequest.5.1")
* 发送GET请求获取内容
loXmlHttp.open("GET", lcUrl, .F.)
loXmlHttp.send("")
=sleep(8000) &&确保下载完成,如果网速慢,适当加长时间。
lcContent = loXmlHttp.responseText

* 将获取的内容写入到HTML文件对象中
loHtml.write(lcContent)

* 关闭HTML文件对象
loHtml.close()
RELEASE loXmlhttp

* 现在可以使用loHtml对象操作文档了
* ? "文档内容:", loHtml.body.innerHTML
* 获得章节内容
lochapter=lohtml.getElementById("chapter")
lcContent=lochapter.innerHTML
lcContent=STRTRAN(lcContent,"<br>","")
lcContent=STRTRAN(lcContent,"&nbsp;","")
*?lcContent
STRTOFILE(lcContent,"wanglaoshi.txt")
*MODIFY FILE wanglaoshi.txt

* 查看分几篇
lapage=ohtml.getElementsByClassName("m-page")
lopage=lapage.item(0)
lcnext=lopage.innerhtml
lcnext=STREXTRACT(lcnext,"<A","</A>",3)
lcnext=JUSTSTEM(lcnext) &&下一章文件名
lccurrent=JUSTSTEM(lcurl) &&当前文件名
IF STREXTRACT(lcnext,"","_")==IIF(EMPTY(STREXTRACT(lccurrent,"","_")),;
    lccurrent,STREXTRACT(lccurrent,"","_"))
   
    MESSAGEBOX("下一页网址:"+STRTRAN(lcurl,lccurrent,lcnext))
    *继续爬,代码略。。。
ELSE
    MESSAGEBOX("下一章网址:"+STRTRAN(lcurl,lccurrent,lcnext))   
ENDIF
RELEASE lohtml

MODIFY FILE wanglaoshi.txt
* 查看下载的html文件内容
*STRTOFILE(lcContent,"wanglaoshi.html")
*MODIFY FILE wanglaoshi.html
#6
王咸美2025-12-06 03:54
谢谢各位高手的热心指导!各章节小说网页上虽没有“下一页”,但是点击“下一章”会进入当前章节的第二页第三页。假如当前页为...4966.html(当前页),点击“下一章”,会出现 ...4966_2.html(第二页),再次点击“下一章”,会出现 ...4966_3.html(第三页),直到本章节完毕,再次点击“下一章”才真正跳转进入下一章页面。

[此贴子已经被作者于2025-12-6 04:02编辑过]

#7
吹水佬2025-12-06 10:29
回复 5楼 sam_jiang
内容不对
只有本站会员才能查看附件,请 登录

只有本站会员才能查看附件,请 登录

<div id="chapter"> 的 innerTEXT 看似是“误导”的
只有本站会员才能查看附件,请 登录


正确的在 <div id="ad"> 动态的

只有本站会员才能查看附件,请 登录




#8
sam_jiang2025-12-06 20:54
没检查,爬到内容就以为是成功了
#9
吹水佬2025-12-06 23:17
以下是引用sam_jiang在2025-12-6 20:54:09的发言:

没检查,爬到内容就以为是成功了

动态用脚本生成的,那就偷懒照模照样做好了。
只有本站会员才能查看附件,请 登录

程序代码:

DECLARE long URLDownloadToFileA  IN urlmon  long,string,string,long,long
DECLARE long DeleteUrlCacheEntry IN wininet string
url = "http://www./6780946/3664234966.html"
outFile = "C:\_temp\tmp.html"
dom = CREATEOBJECT("htmlfile")
STRTOFILE(getText(getHtml(url, outFile)), outFile)
MODIFY FILE (outFile)
CLEAR ALL
RETURN

FUNCTION getText(html)
    html = STRCONV(html, 11)
    content = STREXTRACT(html, [rr('], [')])
    TEXT TO jsCode TEXTMERGE NOSHOW PRETEXT 15
        var content = "<<content>>";
        content = window.atob(content);
        var arrays = new Array();   
        arrays = content.split(',');   
        var $chapter = document.getElementById("chapter").innerHTML;   
        document.getElementById("chapter").innerHTML = $chapter.replace(new RegExp(/\[(.*?)\]/), '$1');   
        $chapter = $chapter.replace(new RegExp(/\[.*?\]/), '');   
        var datas = [];   
        datas = $chapter.split('<br><br>');   
        var $html = '';   
        var el = arrays[0];   
        var len = datas.length;   
        for (var i = 1; i <= len; i++) {        
            $html += datas[arrays[i] - el] + '<br/><br/>';   
        }   
        document.getElementById("ad").innerHTML = $html;   
    ENDTEXT
    html = STRTRAN(html, "<script", "<soript")
    dom.write(html)
    dom.parentWindow.execScript(jsCode)
    content = dom.getElementById("ad").innerTEXT
    dom.close
    RETURN content
ENDFUNC

FUNCTION getHtml(url, outFile)
    IF URLDownloadToFileA(0, url, outFile, 0, 0)==0
        DeleteUrlCacheEntry(url)
        RETURN FILETOSTR(outFile)
    ENDIF
    RETURN ""
ENDFUNC




[此贴子已经被作者于2025-12-6 23:46编辑过]

#10
吹水佬2025-12-06 23:28
那段文本内容按行打乱顺序,每行按行号动态编码。
那段脚本就是解码整理各行顺序后写入 <div id='ad'> 。
解码算法好像是,按顺序取(某行编码 - 第一行编码) = 乱序文本行的行号。  
#11
schtg2025-12-07 06:08
学习啦,谢谢!
#12
王咸美2025-12-07 12:35
谢谢!但对于windows xp系统、低版本IE无法正常运行,不知对于这种情况,代码如何修改?
#13
吹水佬2025-12-07 12:49
以下是引用王咸美在2025-12-7 12:35:35的发言:

谢谢!但对于windows xp系统、低版本IE无法正常运行,不知对于这种情况,代码如何修改?

使用 IE 的东西,现在 windows 10 也不好玩,还用 XP ?

#14
foxfans2025-12-07 12:59
只有本站会员才能查看附件,请 登录

WinHttp.WinHttpRequest.5.1组件就可以。算法可以用二个数组分别存储文章列表和对应序号数组进行对应提取,最后拼接即可。
#15
吹水佬2025-12-07 20:49
以下是引用王咸美在2025-12-7 12:35:35的发言:

谢谢!但对于windows xp系统、低版本IE无法正常运行,不知对于这种情况,代码如何修改?

没有XP测试不了,应该是可以运行的。
可以将运行出现的异常问题细说一下,逐个问题探讨解决,有弹出异常提示时截图看看。
#16
yiyanxiyin2025-12-09 11:05
这个跟写外挂一个道理,只是技术上更简单
首页原理:每一行顺序由这个数组来重定:21,33,60,52,29,48,30,31,22,58,38,36,24,35,28,21,45,61,54,32,39,53,55,25,23,50,34,51,26,56,41,59,46,27,37,40,49,47,43,44,42,57 第一个数为基数, 后面的每个数都会减这个基数, 然后形成新的顺序
后面每一页这个数组的数据都会变, 但逻辑不变,  得到了这个数组并知道了这个逻辑, 可以用vpf来实现文本重排(不需要vpf里面签入js, atob在ie8及以下浏览器中好像是不支持的, vfp解码Base64应该没问题吧),那xp是绝对没问题的

这种动态js形成的类容就是反爬的典型方式,  以后稍微改一下这个算法, 你又要调整代码, 但这个都没啥用, 直接解析html和js, 等js执行完了,再去取内容就行了, 但你的编程组件要能解析js, 纯文本分析才需要分析他的js逻辑.
还有, 做得好一点的, 直接将小说内容输出到canvas里面, 你根本不能直接取到文本, 你得深入分析小说内容是怎么显示出来得, 就跟上面的分析js一样, 但最直接简单的方法是使用ocr

[此贴子已经被作者于2025-12-9 11:46编辑过]

#17
吹水佬2025-12-09 12:11
核心就几句:
chapter = html.getElementById("chapter").innerText
chapter = STRTRAN(chapter, STREXTRACT(chapter, "[","]",1,4), "")
content = STRCONV(ALLTRIM(html.getElementById("ad").innerText, "_ii_rr('", "');"), 14)
ALINES(arrays, content, 5, ",")
lines = ALINES(aDatas, chapter, 5)
code1 = VAL(arrays[1])
chapter = ""
FOR i=2 TO lines+1
    chapter = chapter + aDatas[VAL(arrays[i]) - code1 + 1] + 0h0D0A0D0A
ENDFOR
#18
yiyanxiyin2025-12-09 13:48
以下是引用吹水佬在2025-12-9 12:11:04的发言:

核心就几句:
chapter = html.getElementById("chapter").innerText
chapter = STRTRAN(chapter, STREXTRACT(chapter, "[","]",1,4), "")
content = STRCONV(ALLTRIM(html.getElementById("ad").innerText, "_ii_rr('", "');"), 14)
ALINES(arrays, content, 5, ",")
lines = ALINES(aDatas, chapter, 5)
code1 = VAL(arrays[1])
chapter = ""
FOR i=2 TO lines+1
    chapter = chapter + aDatas[VAL(arrays) - code1 + 1] + 0h0D0A0D0A
ENDFOR
对就这个意思, 不需要任何js支撑, 直接vfp代码倒腾纯文本
#19
吹水佬2025-12-10 00:01
章节有点多,试了几章没耐心试下去。
只有本站会员才能查看附件,请 登录

只有本站会员才能查看附件,请 登录

程序代码:

** 从第一章开始
cUrl = "http://www./6780946/3664234966.html"
http = CREATEOBJECT("WinHttp.WinHttpRequest.5.1")
html = CREATEOBJECT("htmlfile")
html.designmode = "on"
html.write(".")
outFile = "C:\_temp\仙道九绝.dbf"
CREATE TABLE (outFile) (标题 V(50), 网址 V(50), 内容 M)
getHtmlText(cUrl)
SELECT * FROM 仙道九绝
CLOSE TABLES ALL
CLEAR ALL
RETURN

FUNCTION getHtmlText(cUrl)
    PRIVATE cHtml, host, purl, chapters, title, pages, n, m
    cHtml = ""
    host  = "http://" + STREXTRACT(cUrl, "http://", "/")
    purl  = host+"/6780946/"
    pages = getPages(purl)
    IF pages == 0
        RETURN
    ENDIF
    chapters = ""
    chapterUrl = cUrl
    title = ""
    wait = CREATEOBJECT("wait_form")
    wait.show
    n = 1                            && 章节计数
    m = wait.statbox.width / pages   && 进度条单位
    DO WHILE !(cUrl==purl) AND getHtmlTextByUrl(cUrl,@cHtml)
        cUrl = getChapter(cHtml, cUrl)
        #if 0    && 调试用
        IF n>10
            exit
        ENDIF
        #endif
    ENDDO
    wait.release
ENDFUNC

FUNCTION getChapter(cHtml, cUrl)
    html.getElementsByTagName("body").item[0].innerHTML = cHtml
    LOCAL chapter, content
    title   = STREXTRACT(html.getElementsByTagName("title").item[0].innerText, "_", "_")
    chapter = html.getElementById("chapter").innerText
    chapter = STRTRAN(chapter, STREXTRACT(chapter, "[","]",1,4), "")
    content = STRCONV(ALLTRIM(html.getElementById("ad").innerText, "_ii_rr('", "');"), 14)
    ALINES(arrays, content, 5, ",")
    lines = ALINES(aDatas, chapter, 0h0D0A0D0A)
    code1 = VAL(arrays[1])
    chapter = ""
    FOR i=2 TO lines+1    && 取一页
        chapter = chapter + aDatas[VAL(arrays[i]) - code1 + 1] + 0h0D0A0D0A
    ENDFOR
    chapters = chapters + RTRIM(chapter,0h0D0A)    && 每章各页
    page = html.getElementsByClassName("m-tpage").item[0].getElementsByTagName("a").item[2].getAttribute("href")
    IF !(LEFT(page,AT("_",page)-1) $ cUrl)
        INSERT INTO 仙道九绝 VALUES (title, chapterUrl, chapters)
        chapters = ""
        chapterUrl = host + page  && 下一章
        wait.msg.caption = title+0h0D0D+chapterUrl
        wait.statbar.width = m * n
        n = n+1
    ENDIF
    RETURN host + page    && 下一页
ENDFUNC

FUNCTION getHtmlBodyByUrl(cUrl, pHtml)
    RETURN getHtmlByUrl(cUrl, @pHtml, 0)
ENDFUNC

FUNCTION getHtmlTextByUrl(cUrl, pHtml)
    RETURN getHtmlByUrl(cUrl, @pHtml, 1)
ENDFUNC

FUNCTION getHtmlByUrl(cUrl, pHtml, nFlags)
  TRY
    pHtml = ""
    LOCAL err
    err = .f.
    http.open("GET", cUrl, 0)
    http.send()
    IF http.status == 200
        IF nFlags == 0
            pHtml = http.ResponseBody
        ELSE
            pHtml = http.ResponseText
        ENDIF
    ENDIF
  CATCH
    err = .t.      
  ENDTRY
    IF err
        MESSAGEBOX("获取网页失败"+0h0D+cUrl)  
    ENDIF
    RETURN !err
ENDFUNC

FUNCTION getPages(purl)
    LOCAL pHtml
    IF getHtmlTextByUrl(purl, @pHtml)
        html.getElementsByTagName("body").item[0].innerHTML = pHtml
        RETURN VAL(STREXTRACT(html.getElementById("play_0").getElementsByTagName("li").item[0].innerText,"第","章"))
    ENDIF
    RETURN 0
ENDFUNC

DEFINE CLASS wait_form as Form
    width = 500
    height = 150
    AutoCenter = .t.
    showwindow = 0
    windowtype = 0
    titlebar = 0
    movable = .f.
    borderstyle = 2
    alwaysontop = .t.
    ADD OBJECT msg as label WITH left=20,top=20,width=460,height=60,;
        caption="请稍候......",alignment=2,fontsize=12
    ADD OBJECT statbox as shape WITH left=20,top=100,width=460,height=22,;
        SpecialEffect=1,BackColor=0x00FFFFFF,BorderColor=0x00C0C0C0
    ADD OBJECT statbar as shape WITH left=20,top=100,width=0,height=22,;
        SpecialEffect=1,BackColor=0x00C08000,BorderColor=0x00C0C0C0
    PROCEDURE DblClick
        this.Release
    ENDPROC
ENDDEFINE




[此贴子已经被作者于2025-12-10 11:47编辑过]

#20
yiyanxiyin2025-12-10 10:07
既然使用了WinHttp.WinHttpRequest.5.1就不要使用htmlfile这个组件了,用winhttp的目的就是不解析html, 而htmlfile会解析html和js, 因为htmlfile的功能更强大,当然会很慢,
另外, 必须加入防反爬机制(1.模拟浏览器请求头,2.随机时间间隔的请求,不能太频繁),最好做到类似人类行为, 否则ip很容易被封,还应该加入错误处理机制,如果一个页没下载成功,那需要反复下载直到成功才继续下一页,否则爬取的内容没法用

[此贴子已经被作者于2025-12-10 10:19编辑过]

#21
sam_jiang2025-12-10 11:01
以前没有太关注反爬技术,现在总算接触到了。我在想,既然有利用js打乱文章正常顺序的反扒技术,那么我们是不是可以把包含js代码的网页交由js解析器先解析,得到纯HTML的网页后再爬取文字内容呢?这样就避免了人家改了排序逻辑我们代码就无效的问题了!

说到反扒技术,我想起之前有人想根据书籍的条形码获取书籍信息的文章,国家图书馆的反扒技术是动态改变链接地址,只有网页上点击搜索才会跳出一个正确链接,若是爬取了这个链接,下次使用又不对了!这个反扒技术该如何破解,请大侠们赐招
#22
吹水佬2025-12-10 11:43
“反爬”是相对的。
看源代码有帮助,用 DevTools 工具。
“反爬”通常是对解析器有限制,如果不是按他的要求正常浏览,解析结果也不正常。
所以hmtl文本载入解析器之前最好要禁止解析器执行脚本(禁止之后就不能执行脚本了),但可以试试嵌入脚本代码执行或用解析器执行脚本代码。
见过有些网站要用指定浏览器,用其他浏览器进去会提示用他指定的浏览器。




[此贴子已经被作者于2025-12-10 16:07编辑过]

#23
吹水佬2025-12-10 11:48
19楼提速更新
#24
吹水佬2025-12-10 12:11
回复 20楼 yiyanxiyin
19楼更新速度明显加快。
慢的问题不在 WinHttp.WinHttpRequest.5.1,也不在 htmlfile。
看了一下MS的相关文档,对于类似这些COM对象的使用通常只需 CreateObject 一次实例就可以,除非是多线程或多进程通讯才需要 CreateObject 多个实例。
使用解析器(htmlfile)的好处是应变能力强,取数通过 id、tag、class 对象进行,比较可靠,网页设计通常不会随便去改这些对象。相对用vfp函数去解析html文本,变数会大些,速度也较慢,尤其是对一些数据量大的JSON解析。



[此贴子已经被作者于2025-12-10 13:34编辑过]

#25
吹水佬2025-12-10 13:57
以下是引用yiyanxiyin在2025-12-9 13:48:14的发言:

对就这个意思, 不需要任何js支撑, 直接vfp代码倒腾纯文本

要看具体情况,相对来说,用JS代码比用VFP代码速度快些,效率高些。也不用写那么多代码,随手可得。
但这个小说站的脚本也有点怪,看看他的解码函数:
程序代码:

function _ii_rr(content) {
    content = window.atob(content);
    var arrays = new Array();
    arrays = content.split(',');
    var $chapter = document.getElementById("chapter").innerHTML;
    console.log($chapter);
    document.getElementById("chapter").innerHTML = $chapter.replace(new RegExp(/\[(.*?)\]/), '$1');
    $chapter = $chapter.replace(new RegExp(/\[.*?\]/), '');
    var datas = [];
    datas = $chapter.split('<br><br>');
    var $html = '';
    var el = arrays[0];
    var len = datas.length;
    for (var i = 1; i <= len; i++) {
        $html += datas[arrays[i] - el] + '<br/><br/>';
    }
    document.getElementById("ad").innerHTML = $html;
    document.getElementById('chapter').style.color = '#FBF6EC';
    document.getElementById("chapter").style.height = "5px";
    document.getElementById('chapter').style.display = 'none';
}

其中 console.log($chapter); 这句就有点费解?与控制台有什么关系,会减速。
只爬的话,下面几句多余的。
    console.log($chapter);
    document.getElementById("chapter").innerHTML = $chapter.replace(new RegExp(/\[(.*?)\]/), '$1');
    document.getElementById('chapter').style.color = '#FBF6EC';
    document.getElementById("chapter").style.height = "5px";
    document.getElementById('chapter').style.display = 'none';
不管用什么代码去解析, datas = $chapter.split('<br><br>'); 这句要理解清楚,意思是生成由两个换行作为分隔符的数组,数组包括空元素(空行)。
#26
yiyanxiyin2025-12-10 14:43
http = CREATEOBJECT("WinHttp.WinHttpRequest.5.1")
html = CREATEOBJECT("htmlfile")
这两个组件的主要区别就是, 第一个不会解析html和js(不会执行_ii_rr()这个js函数,也就是_ii_rr()需要自己实现,才能按正常顺序获取到小说内容), 第二个会.  使用第一个适合做文本分析,而不是操作html, 它仅仅将静态html内容下载下来,后面要爬什么都要自己分析文本,不用说性能肯定更高,第二个组件会渲染html,包括执行js(所以可以在等到页面js执行完成后再读html,也就是等那个_ii_rr()执行完成,那么这个时候读到的小说内容就是按正常顺序显示的), 所以用第一个组件可以不用第二个,  如果系统版本低,比如是xp,可能不能正常渲染html和js,就只使用第一个组件就可以了, 第二个组件能用的化,在某些时候明显更方便,但有时候却不适用

既然用了第二个,那么就应该等到js执行完再去取ad的内容就行了,没必要自己去实现_ii_rr()

这就是比较明显的爬虫的两种不同的技术嘛(1.纯文本分析,不需要浏览器去渲染,往往用regexp提取内容 2.然浏览器去渲染,等渲染完去爬取结果,当然往往使用无头浏览器速度会快一些)

另:console.log($chapter);这个仅仅是方便在浏览器控制台查看信息,不开控制台对性能基本没任何影响

[此贴子已经被作者于2025-12-10 16:47编辑过]

#27
吹水佬2025-12-10 15:53
回复 26楼 yiyanxiyin
Windows HTTP 服务主要包含两大部分:WinHTTP C/C++ API 和 WinHttpRequest 对象。
WinHttpRequest 对象是封装了 WinHTTP API 的 COM 对象。

htmlfile 对象主要部分是MSHTML的脚本对象接口 IHTMLDocument2 对象,相当于IE的Document对象,Document对象见得多了。
IE核心主要是有 Windows HTTP 服务和 MSHTML。
Windows HTTP 起网络通信,HTTP协议也是基于TCP/IP协议。
MSHTML 起解析执行 html 文本。

所以,WinHttp.WinHttpRequest.5.1 与 htmlfile 是有紧密关系的,不可孤立看待。
#28
sam_jiang2025-12-10 15:57
回复 26楼 yiyanxiyin
受教了,原来第二个组件可以等待js执行完啊!有空试试看!
#29
吹水佬2025-12-10 15:58
简单嵌入脚本代码示例,看看是不是很简单。
如果原脚本代码没什么问题可以直接下载用。
程序代码:

** 测试用 JS 代码,参考 http://www./js/zepto.js 修改。
** 模拟写 JS 文件,实际应用通常先写好 JS 文件。
TEXT TO jsCode TEXTMERGE NOSHOW PRETEXT 15
    <script>
    var content = document.getElementById("ad").innerText;
    eval(content);

    function _ii_rr(content) {   
        content = window.atob(content);   
        var arrays = new Array();   
        arrays = content.split(',');   
        var $chapter = document.getElementById("chapter").innerHTML;   
        $chapter = $chapter.replace(new RegExp(/\[.*?\]/), '');   
        var datas = [];   
        datas = $chapter.split('<br><br>');   
        var $html = '';   
        var el = arrays[0];   
        var len = datas.length;   
        for (var i = 1; i <= len; i++) {        
            $html += datas[arrays[i] - el] + '<br/><br/>';   
        }   
        document.getElementById("ad").innerHTML = $html;
    }
    </script>
ENDTEXT
jsFile = "C:\_temp\jscode.js"
STRTOFILE(jsCode, jsFile)
** 模拟写 JS 文件结束

** 爬代码
cUrl = "http://www./6780946/3664234966.html"
http = CREATEOBJECT("WinHttp.WinHttpRequest.5.1")
http.open("GET", cUrl, 0)
http.send()
IF http.status != 200
    RETURN
ENDIF
dom = CREATEOBJECT("htmlfile")
dom.write(STRTRAN(http.ResponseText, "script", "soript"))
dom.writeln(FILETOSTR(jsFile))
outFile = "C:\_temp\tmp.txt"
STRTOFILE(dom.getElementById("ad").innerText, outFile)
MODIFY FILE (outFile)
CLEAR ALL
RETURN
#30
sam_jiang2025-12-10 16:06
回复 29楼 吹水佬
这样就可以了吗?
#31
吹水佬2025-12-10 16:09
以下是引用sam_jiang在2025-12-10 16:06:12的发言:

这样就可以了吗?

简单示例,能运行吗?

#32
吹水佬2025-12-10 16:18
回复 29楼 吹水佬
测试无所谓,实际应该分两步做。
程序代码:

** 测试用 JS 代码,参考 http://www./js/zepto.js 修改。
** 模拟写 JS 文件,实际应用通常先写好 JS 文件。
TEXT TO jsCode TEXTMERGE NOSHOW PRETEXT 15
    <script>
    var content = document.getElementById("ad").innerText;
    eval(content);

    function _ii_rr(content) {   
        content = window.atob(content);   
        var arrays = new Array();   
        arrays = content.split(',');   
        var $chapter = document.getElementById("chapter").innerHTML;   
        $chapter = $chapter.replace(new RegExp(/\[.*?\]/), '');   
        var datas = [];   
        datas = $chapter.split('<br><br>');   
        var $html = '';   
        var el = arrays[0];   
        var len = datas.length;   
        for (var i = 1; i <= len; i++) {        
            $html += datas[arrays[i] - el] + '<br/><br/>';   
        }   
        document.getElementById("ad").innerHTML = $html;
    }
    </script>
ENDTEXT
jsFile = "C:\_temp\jscode.js"
STRTOFILE(jsCode, jsFile)
** 模拟写 JS 文件结束

程序代码:

** 爬代码
cUrl = "http://www./6780946/3664234966.html"
http = CREATEOBJECT("WinHttp.WinHttpRequest.5.1")
http.open("GET", cUrl, 0)
http.send()
IF http.status != 200
    RETURN
ENDIF
jsFile = "C:\_temp\jscode.js"
dom = CREATEOBJECT("htmlfile")
dom.write(STRTRAN(http.ResponseText, "script", "soript"))
dom.writeln(FILETOSTR(jsFile))
outFile = "C:\_temp\tmp.txt"
STRTOFILE(dom.getElementById("ad").innerText, outFile)
MODIFY FILE (outFile)
CLEAR ALL
RETURN



[此贴子已经被作者于2025-12-10 16:20编辑过]

#33
yiyanxiyin2025-12-10 17:26
爬虫, 还是用python吧, 你会发现python下的工具用起来才顺手, 且和ai配合很好, 你都不用写代码, 就楼主这个小说, 少则几分钟,多则10多20分钟代码都写好了,ai自动分析html内容, 自动写好代码,并运行,你只要和ai多交流几句,反爬的代码都能给你写好,容错代码也没问题, 然后就将小说自动爬下来放入文件中, 都不用你敲一行代码.  所以,重要的是要知道实现原理, 能看懂代码, 能理解代码, 才能和ai沟通, 当然不会的人有ai也还是不会, ai永远就是个助手,他只能按你的要求写, 你才是关键.
#34
吹水佬2025-12-10 20:20
回复 33楼 yiyanxiyin
底层逻辑都差不多,无非是 HTTP + HTML 的东西,再下一层就是 TCP/IP + HTML。
python在这方面语言表达能力比vfp强。
python强在于经过很多先行者经过长时间的经验积累,形成大量的类库,后来者才会感觉不费心思就可以随手可得。
vfp弱在于旱就被判死刑,谈不上有什么经验积累,更不能形成大量类库。想当初 dBASE 就有 CALL 执行汇编代码,DOS环境可以开发出图形界面。
假若vfp还在活,凭借vfp的表达能力加数据库加UI集于一身的功能,vfp肯定不会示弱,可惜我只是在做白日梦。
python与vfp都是解释语言,本质上没什么大差别。vfp也可以用其他语言扩展其功能和加强性能,还有COM编程能力,vfp发挥得好还是大有作为的。但是,也只能活在 windows 32位 的环抱里。


#35
吹水佬2025-12-10 21:19
以下是引用yiyanxiyin在2025-12-10 14:43:05的发言:

http = CREATEOBJECT("WinHttp.WinHttpRequest.5.1")
html = CREATEOBJECT("htmlfile")

既然用了第二个,那么就应该等到js执行完再去取ad的内容就行了,没必要自己去实现_ii_rr()

真能“反爬”的肯定不会让你用 htmlfile 能正常解析执行脚本。
如果装入html文本前不禁止 htmlfile 执行脚本,结果想要的东西都不见了,试试看。
#36
yiyanxiyin2025-12-10 23:05
再强的反爬也不能将正常的人为行为反掉,只要模拟出人为行为反爬就不起作用了。那个网站的反爬技术明显比较低级,只能反一般的爬虫,你用htmlfile之所以不正常,应该是这个组件不能下载网页内容,只能靠winhttp给他提供html,但html相关的js文件你都没下载下来,所以js不会正常。所以最好要用无头浏览器呢
#37
吹水佬2025-12-10 23:49
以下是引用yiyanxiyin在2025-12-10 23:05:11的发言:

再强的反爬也不能将正常的人为行为反掉,只要模拟出人为行为反爬就不起作用了。那个网站的反爬技术明显比较低级,只能反一般的爬虫,你用htmlfile之所以不正常,应该是这个组件不能下载网页内容,只能靠winhttp给他提供html,但html相关的js文件你都没下载下来,所以js不会正常。所以最好要用无头浏览器呢

重温一下27楼说的,尤其是最后一句。
web的东西都离不开http和html,一个是网络通讯部分,一个是解析执行部分,他们是分工协作完成web任务的。
http也就是你说的下载网页之一,js文件怎会不下载呢?只是在什么时候下载的问题。html文本的<script>有src属性的,这就要看 htmlfile了,它是一个解析执行器,它载入html文本解析时,须要下载时它会去下载。所以,http与html是密不可分的。
#38
吹水佬2025-12-11 00:01
35楼说的有无去试试看?
下载得到的html文本,直接用htmlfile的write载入,看看载入前后的html文本就清楚,按你说的如果无下载js执行理应是无变化。但结果如何?
#39
吹水佬2025-12-11 00:22
27楼提到的MSHTML的脚本对象接口有不少,htmlfile不只是使用IHTMLDocument2接口对象,其中的parentWindow属性就是调用IHTMLWindow2接口。
#40
吹水佬2025-12-11 00:38
以下是引用yiyanxiyin在2025-12-10 23:05:11的发言:

再强的反爬也不能将正常的人为行为反掉,只要模拟出人为行为反爬就不起作用了。

会不会有密文html的浏览器,那时只能拷屏了?如果截屏都禁了,那就只能拍照?真的事在人为,不能将正常的人为行为反掉。

[此贴子已经被作者于2025-12-11 00:42编辑过]

#41
yiyanxiyin2025-12-11 07:26
htmlfile这个组件是不会下载页面中引入的js文件的 图片之类的也不会下载 要不然你就不会使用winhttp了
#42
yiyanxiyin2025-12-11 07:29
html和js以及图片之类的都得下载到本地才会被浏览器解析执行
#43
吹水佬2025-12-11 12:09
htmlfile是可以打开浏览器窗口的,39楼有提到htmlfile的parentWindow属性就是调用IHTMLWindow2接口。
IHTMLWindow2接口就是window对象。但现在想要的是无窗口的“爬行”。
简单的可以这样试试:

url = "http://www.
dom = CREATEOBJECT("htmlfile")
dom.parentWindow.navigate(url)

它还有一个open方法,可以在后台无窗口执行,但在vfp调用有异常?有空再试试。
#44
吹水佬2025-12-11 16:22
查了一下相关资料,open方法要IE支持,我的windows系统升级升级就没有IE了,InternetExplorer.Application 也不见了。
但 WebBrowser 还在,等 WebBrowser 也死掉时 htmlfile 应该也会死。看来要搞个 WebView2 的无头虫来玩玩。
没法测试 open方法了,只能下载脚本注入。
程序代码:

outFile = "C:\_temp\tmp.txt"
url_html = "http://www./6780946/3664234966.html"
url_js = "http://www./js/zepto.js"
http = CREATEOBJECT("WinHttp.WinHttpRequest.5.1")
http.open("GET", url_html, 0)
http.send()
html_str = http.ResponseText
http.open("GET", url_js, 0)
http.send()
js_str = http.ResponseText
html = CREATEOBJECT("htmlfile")
html.write("<script>" + js_str + "</script>")
html.writeln(html_str)
INKEY(0.001)
html.close()
STRTOFILE(html.getElementById("ad").innerText,outFile)
MODIFY FILE (outFile)





[此贴子已经被作者于2025-12-11 16:34编辑过]

#45
yiyanxiyin2025-12-11 16:52
htmlfile是一个过时的组件, 性能很低, 据我查到的资料是不支持发送请求的,  WebView2应该可以实现无头模式,适合动态页面(js生成的页面)爬取,可以一试
#46
吹水佬7 天前 10:35
回复 45楼 yiyanxiyin
对于vfp来说htmlfile永不过时,vfp能用好用的html解析器不多见(其他编程语言支持的不算),htmlfile还是可取的。
在vfp言vfp,如果跳出vfp来说,肯定不会用htmlfile。
htmlfile可以看成是一个精简的浏览器核心部件,他能解析执行hmtl,并且能打开浏览窗口浏览。
至于htmlfile的性能方面,这就要用历史的观点去看问题,要明白她也有过辉煌的时期,就好像对vfp一样看待。
顺便说说使用htmlfile要注意的一些问题。
htmlfile是与IE有联系的,应用程序使用IE内核版本的高低对htmlfile的解析能力也随之变化,最好修改注册表将应用程序使用IE内核的版本能改多高就改多高。这个IE内核版本的设置是针对应用程序来说的,如对vfp9开发环境来说是设置为vfp9.exe的值,如果是编译后xxxx.exe程序就是xxxx.exe的值。
如何修改注册表本论坛有讨论过。





#47
吹水佬7 天前 11:15
以下是引用yiyanxiyin在2025-12-11 16:52:19的发言:

 WebView2应该可以实现无头模式,适合动态页面(js生成的页面)爬取,可以一试

无头虫的WebView2比有头的就更简单些。现在当保母不容易,买菜做饭带小孩样样要到位,家事繁忙有空可试试。
以前发过一个带源码的WebView2的demo,应该也可以用用。
顺便提提那个WebView2的demo,事件大都是用异步消息的,最好定义某事件标志,再定义一个wait函数来判断事件的执行完成情况,从而达到同步效果。
wait函数的设计要有点技巧,不能用阻塞式的循环或延时来等待检测事件标志,比较方便的可用vfp的timer来定时检测事件标志。
如在timer事件,例如myNavigate方法检测NavigationCompleted事件页面是否已经载入完成(其他事件类同)。
相关代码大概如下:

** myNavigate 方法
LPARAMETERS cUrl, nDelay
this.isNavigationCompleted = .f.
WebView_Navigate(STRCONV(cUrl+0h00,5))  && 打开html
IF !EMPTY(nDelay)
    this.myWaitNavigation(nDelay)
ENDIF
RETURN this.isNavigationCompleted

** OnNavigationCompleted 事件
this.isNavigationCompleted = .t.


** timer事件
INKEY(0.01)
IF !EMPTY(thisform.isNavigationCompleted)
    this.Enabled = .f.
ENDIF

wait函数:
* myWaitNavigation(nDelay) 方法
* 检测 OnNavigationCompleted 事件的 this.isNavigationCompleted 状态
* timer1.Enabled = .t. 启动 timer 事件检测 this.isNavigationCompleted 是否有返回数据
LPARAMETERS nDelay    &&延时(秒)
LOCAL t
t = SECONDS()
this.timer1.Enabled = .t.
DO WHILE this.timer1.Enabled AND (SECONDS()-t)<nDelay
    DOEVENTS
    WebView_delay(0.1)
ENDDO
RETURN this.isNavigationCompleted


#48
iswith7 天前 11:24
这是我使用netpy在vfp中爬取的,这是通用模块,把每个章节的对应的连接获取成表,接下未做,相信你能知道接下来如何去做了。
只有本站会员才能查看附件,请 登录
#49
吹水佬7 天前 11:45
回复 48楼 iswith
要装 Python 的吧
懂vfp的学用Python不难,很多库类拿来就用。还是一种“胶水”语言,可以解析其他语言脚本。
取网页三两句就可以,急用先学,最好学一点,编程语言结构都差不多。
现在有AI编程就更容易学。



[此贴子已经被作者于2025-12-12 11:46编辑过]

#50
吹水佬5 天前 08:56
简单试了一下 WebView2 还可以
只有本站会员才能查看附件,请 登录

只有本站会员才能查看附件,请 登录

只有本站会员才能查看附件,请 登录


#51
iswith4 天前 14:54
多学习几种语言没坏处!py相对vfp玩家来说没有难度!py有支持库可以随项目发包与发布vfp支持库一个道理!别造轮子,别的语言有非常完善且好的资源学习应用及可。不要什么都拿vfp去完成,vfp有他自己的优点,会调用及可!
12