欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

從一個(gè)爬蟲(chóng)開(kāi)始ChatGPT的編程秀

 更新時(shí)間:2023年03月21日 14:10:41   作者:仝鍵  
這篇文章主要為大家介紹了從一個(gè)爬蟲(chóng)開(kāi)始ChatGPT的編程秀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

思考問(wèn)題域

我要寫(xiě)一個(gè)爬蟲(chóng),把ChatGPT上我的數(shù)據(jù)都爬下來(lái),首先想想我們的問(wèn)題域,我想到幾個(gè)問(wèn)題:

  • 不能用HTTP請(qǐng)求去爬,如果我直接
  • 用HTTP請(qǐng)求去抓的話,一個(gè)我要花太多精力在登錄上了,而我的數(shù)據(jù)又不多,另一個(gè),現(xiàn)在都是單頁(yè)引用,你HTTP爬下來(lái)的根本就不對(duì)啊。
    • 所以最好是自動(dòng)化測(cè)試的那種方式,啟動(dòng)瀏覽器去爬。
  • 但是我又不能保證一次把代碼寫(xiě)成功,反復(fù)登錄的話,會(huì)被網(wǎng)站封號(hào),就幾個(gè)數(shù)據(jù),不值當(dāng)?shù)摹?/li>

所以總的來(lái)說(shuō)我需要一個(gè)這樣的流程:

從流程上我們是不是可以看出,這個(gè)流程跟我們用WebConsole試驗(yàn)一段代碼的過(guò)程很像?

從這種相似性可以看出,我需要一個(gè)類(lèi)似WebConsole的東西來(lái)實(shí)現(xiàn)我要的效果,這個(gè)東西學(xué)名叫REPL(Read–eval–print loop),不過(guò)你不知道這個(gè)名字也無(wú)所謂,不影響。

而且還不止,我需要從文件讀入我的代碼,畢竟沒(méi)有代碼高亮,我可寫(xiě)不好程序。從文件讀入的話,我就可以用vscode給我提供代碼高亮,這豈不是美滋滋。

想到這,如果是以前,我已經(jīng)一個(gè)頭兩個(gè)大了,我只是想抓點(diǎn)東西,你讓我搞個(gè)REPL,這玩意聽(tīng)著就不科學(xué),我一天就晚上有一個(gè)小時(shí)的時(shí)間寫(xiě)點(diǎn)代碼,這玩意太復(fù)雜了,乍一看也不是我業(yè)余時(shí)間能寫(xiě)出來(lái)的。

但是現(xiàn)在,我們有了ChatGPT,那么我接下來(lái)可以用ChatGPT開(kāi)始寫(xiě),簡(jiǎn)單做一個(gè)任務(wù)分解:

  • 寫(xiě)一個(gè)框架,可以最大限度的幫我們提高開(kāi)發(fā)效率
  • 在這個(gè)框架上,一個(gè)場(chǎng)景一個(gè)場(chǎng)景的開(kāi)發(fā)爬蟲(chóng)

用ChatGPT寫(xiě)一個(gè)爬蟲(chóng)

1. 先寫(xiě)一個(gè)框架

首先,我可能不知道這個(gè)東西叫REPL,但是我知道控制臺(tái),那我就這么問(wèn):

如何用nodejs做一個(gè)命令行交互控制臺(tái),執(zhí)行輸入的js代碼

然后我就得到了下面的代碼:

const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: '>> '
});
rl.prompt();
rl.on('line', (line) => {
try {
    const result = eval(line);
    console.log(result);
} catch (err) {
    console.error(err);
}
rl.prompt();
});
rl.on('close', () => {
console.log('Exiting...');
process.exit(0);
});

但是這個(gè)代碼還不夠我用的,我需要文件輸入,所以我可以問(wèn):

如果是讀入文件作為輸入呢?

然后我得到了下面的代碼:

const fs = require('fs');
const fileName = 'path/to/file.js';
const fileContent = fs.readFileSync(fileName, 'utf8');
try {
const result = eval(fileContent);
console.log(result);
} catch (err) {
console.error(err);
}

第一反應(yīng),這個(gè)不是async/await模式調(diào)用的呀,我后面代碼寫(xiě)起來(lái)會(huì)很麻煩,于是我就說(shuō):

我要用fs/promises庫(kù)實(shí)現(xiàn)上面邏輯怎么寫(xiě)呢?

然后我就得到了下面的代碼:

const fs = require('fs').promises;
const fileName = 'path/to/file.js';
const fileContent = await fs.readFile(fileName, 'utf8');
try {
const result = eval(fileContent);
console.log(result);
} catch (err) {
console.error(err);
}

這個(gè)點(diǎn)算是令我滿意了,但馬上我又意識(shí)到不對(duì)勁,我需要執(zhí)行完之后 hang 在那里等待下一次輸入的,他這執(zhí)行完就退出了,于是我又改進(jìn)了的問(wèn)題:

我要實(shí)現(xiàn)的效果是,啟動(dòng)后在命令行里打印一個(gè)“>”然后等待輸入,我敲擊回車(chē),程序就會(huì)讀入代碼中寫(xiě)死的目標(biāo)文件,并執(zhí)行其中的js,然后重新打印一個(gè)“>“等待下一次輸入,無(wú)限循環(huán)。

然后我得到了下面的代碼:

const readline = require('readline');
const fs = require('fs').promises;
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: '> '
});
let filePath = 'path/to/target/file.js';
rl.prompt();
rl.on('line', async () => {
try {
    const fileContent = await fs.readFile(filePath, 'utf8');
    const result = eval(fileContent);
    console.log(result);
} catch (err) {
    console.error(err);
}
rl.prompt();
});
rl.on('SIGINT', () => {
console.log('Exiting...');
rl.close();
process.exit(0);
});

注意,我再第一次的時(shí)候忘記了寫(xiě)”無(wú)限循環(huán)“,所以他寫(xiě)的代碼就只執(zhí)行兩次,仔細(xì)一看,確實(shí)這樣實(shí)現(xiàn)也符合我說(shuō)的邏輯,他沒(méi)有猜測(cè)我要無(wú)限循環(huán),所以在使用的時(shí)候要注意,有時(shí)候沒(méi)得到你想要的結(jié)果可能是你沒(méi)說(shuō)清楚。

那么看起來(lái)到此為止就差不多了,沒(méi)想到也不復(fù)雜。那我測(cè)試一下。

  • 我建立了一個(gè)下面的工程結(jié)構(gòu):
.
├── index.js
└── input
    └── input.js
  • 把文件名寫(xiě)死為input/input.js
  • 執(zhí)行 node index.js

彩蛋:其中有一次執(zhí)行的代碼在后續(xù)執(zhí)行發(fā)現(xiàn)錯(cuò)誤,

發(fā)現(xiàn)下面錯(cuò)誤:

  ```
  /Volumes/MyLab/chatgpt-show/crawler/workspace/v1/index.js:15
  const input = await new Promise(resolve => rl.question('', resolve));
                  ^^^^^
  SyntaxError: await is only valid in async functions and the top level bodies of modules
      at internalCompileFunction (node:internal/vm:73:18)
      at wrapSafe (node:internal/modules/cjs/loader:1149:20)
      at Module._compile (node:internal/modules/cjs/loader:1190:27)
      at Module._extensions..js (node:internal/modules/cjs/loader:1280:10)
      at Module.load (node:internal/modules/cjs/loader:1089:32)
      at Module._load (node:internal/modules/cjs/loader:930:12)
      at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
      at node:internal/main/run_main_module:23:47
  ```

原來(lái)是await用錯(cuò)了,不過(guò)沒(méi)關(guān)系,直接拷給他:

上面的代碼執(zhí)行報(bào)錯(cuò): SyntaxError: await is only valid in async functions and the top level bodies of modules

于是得到了修改后的代碼

2. 在這個(gè)框架上,開(kāi)發(fā)爬蟲(chóng)

那么接下來(lái),我可以試著抓取一下列表了,這部分GPT3.5就不能勝任了,我切換到GPT4,然后問(wèn):

我用puppeteer打開(kāi)一個(gè)網(wǎng)頁(yè),可以直接用document DOM API操作,然后返回?cái)?shù)據(jù),請(qǐng)抓取這個(gè)網(wǎng)頁(yè)的列表,HTML如下:

.....

下面省略是openAI的頁(yè)面代碼,但是呢,因?yàn)樗罅耍也坏貌皇謩?dòng)做了點(diǎn)刪減,比如我列表很長(zhǎng),我就減到只剩3個(gè)元素,其他無(wú)關(guān)的元素的代碼我都刪掉。

經(jīng)過(guò)幾次詢(xún)問(wèn),他給出了我代碼:

const puppeteer = require('puppeteer');
(async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.goto('你的網(wǎng)址'); // 將'你的網(wǎng)址'替換為您要打開(kāi)的頁(yè)面的實(shí)際URL
    const list = await page.evaluate(() => {
        const anchors = document.querySelectorAll('a');
        const items = [];
        for (const anchor of anchors) {
            const itemText = anchor.querySelector('div.flex-1.text-ellipsis.max-h-5.overflow-hidden.break-all.relative');
            if (itemText) {
                const itemTitle = itemText.textContent.trim();
                const itemLink = anchor.href;
                items.push({ title: itemTitle, link: itemLink });
            }
        }
        return items;
    });
    console.log(list);
    await browser.close();
})();

3. 回到任務(wù)1的問(wèn)題域

真到執(zhí)行的時(shí)候又發(fā)現(xiàn)新問(wèn)題,這個(gè)puppeteer我希望他一直開(kāi)著,就不能在input.js里啟動(dòng),我需要在REPL代碼里就執(zhí)行,所以我需要稍微修改下代碼,我說(shuō):

我需要在控制臺(tái)啟動(dòng)的時(shí)候用puppeteer啟動(dòng)一個(gè)瀏覽器,然后每次回車(chē)可以讓文件中的代碼可以通過(guò)puppeteer訪問(wèn)啟動(dòng)的這個(gè)瀏覽器

然后得到了代碼:

async function main() {
const readline = require('readline');
const fs = require('fs').promises;
const puppeteer = require('puppeteer-core');
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
    prompt: '> '
});
const browser = await puppeteer.launch({
    executablePath: '/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome'
});
let filePath = 'path/to/target/file.js';
rl.prompt();
async function executeWithBrowser(browser) {
    const fileContent = await fs.readFile(filePath, 'utf8');
    return eval(`async function (browser) { ${fileContent} }`)(browser);
}
rl.on('line', async () => {
    try {
    const result = await executeWithBrowser(browser);
    console.log(result);
    } catch (err) {
    console.error(err);
    }
    rl.prompt();
});
rl.on('SIGINT', async () => {
    console.log('Exiting...');
    await browser.close();
    rl.close();
    process.exit(0);
});
}
main();

這代碼還是不令我滿意,我又接連問(wèn)了下面幾個(gè)問(wèn)題:

  • 我需要browser是GUI可見(jiàn)的
  • 把path/to/target/file.js 改為 input/input.js
  • 我需要在進(jìn)入input/input.js之前直接啟動(dòng)一個(gè)page,里直接訪問(wèn)page而不是browser
  • 這行代碼: return eval(async function (page) { ${fileContent} })(page);
    報(bào)錯(cuò):
    xxxx 能不能不用eval?
  • 報(bào)錯(cuò): /Volumes/MyLab/chatgpt-show/crawler/workspace/v1/index.js:11
    const browser = await puppeteer.launch({
    ^^^^^
    SyntaxError: await is only valid in async functions and the top level bodies of modules

最后得到了我可以執(zhí)行的代碼。不過(guò)實(shí)際執(zhí)行中還出現(xiàn)了防抓機(jī)器人的問(wèn)題,經(jīng)過(guò)一些列的查找解決了這個(gè)問(wèn)題,為了突出重點(diǎn),這里就不貼解決過(guò)程了,最終代碼如下:

const readline = require('readline');
const fs = require('fs').promises;
// const puppeteer = require('puppeteer-core');
const puppeteer = require('puppeteer-extra')
// add stealth plugin and use defaults (all evasion techniques)
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());
(async () => {
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
    prompt: '> '
});
const browser = await puppeteer.launch({
    executablePath: '/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome',
    headless: false,
    args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-web-security']
});
const page = await browser.newPage();
let filePath = 'input/input.js';
rl.prompt();
async function executeWithPage(page) {
    const fileContent = await fs.readFile(filePath, 'utf8');
    const func = new Function('page', fileContent);
    return func(page);
}
rl.on('line', async () => {
    try {
    const result = await executeWithPage(page);
    console.log(result);
    } catch (err) {
    console.error(err);
    }
    rl.prompt();
});
rl.on('SIGINT', async () => {
    console.log('Exiting...');
    await browser.close();
    rl.close();
    process.exit(0);
});
})();

4. 最后回到具體的爬蟲(chóng)代碼

而既然瀏覽器一直開(kāi)著了,那我們需要執(zhí)行的代碼其實(shí)只有兩個(gè)了:

  • goto_chatgpt.js
(async () => {
    await page.goto('https://chat.openai.com/chat/'); 
})();
  • fetch_list.js
(async () => {
    const list = await page.evaluate(() => {
        const anchors = document.querySelectorAll('a');
        const items = [];
        for (const anchor of anchors) {
            const itemText = anchor.querySelector('div.flex-1.text-ellipsis.max-h-5.overflow-hidden.break-all.relative');
            if (itemText) {
                const itemTitle = itemText.textContent.trim();
                const itemLink = anchor.href;
                items.push({ title: itemTitle, link: itemLink });
            }
        }
        return items;
    });
    console.log(list);
})();

當(dāng)然實(shí)際上fetch_list.js有點(diǎn)問(wèn)題,因?yàn)閛penai做了防抓程序,我們可能很難搞到列表項(xiàng)的鏈接,不過(guò)這個(gè)也不難,我們用名字匹配挨個(gè)點(diǎn)就好了嘛,反正也不多。

比如下面這樣:

(async () => {
    const targetTitle = 'AI Replacing Human';
    const targetSelector = await page.evaluateHandle((targetTitle) => {
        const anchors = document.querySelectorAll('a');
        for (const anchor of anchors) {
            const itemText = anchor.querySelector('div.flex-1.text-ellipsis.max-h-5.overflow-hidden.break-all.relative');
            if (itemText && itemText.textContent.trim() === targetTitle) {
                return anchor;
            }
        }
        return null;
    }, targetTitle);
    if (targetSelector) {
        const box = await targetSelector.boundingBox();
        await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2);
        console.log(`Clicked the link with title "${targetTitle}".`);
    } else {
        console.log(`No link found with title "${targetTitle}".`);
    }
})();

說(shuō)句題外話,上面的代碼很有意思,似乎它為了防止點(diǎn)某個(gè)具體元素不管用,竟然點(diǎn)擊了一個(gè)區(qū)域。

接下來(lái)如果我們想備份我們的每一個(gè)thread就可以在這個(gè)基礎(chǔ)上,讓ChatGPT繼續(xù)給我們寫(xiě)實(shí)現(xiàn)完成即可,這里就不繼續(xù)展開(kāi)了,大家可以自己完成。

回顧一下,我們做了什么,得到了什么?

  • 首先,我們對(duì)問(wèn)題域做了分析,把目標(biāo)網(wǎng)站和工作者我本人以及時(shí)間限制等約束都納入了問(wèn)題域進(jìn)行了分析,得到了一個(gè)方案,然后通過(guò)類(lèi)比發(fā)現(xiàn)我們的方案其實(shí)就是做一個(gè)有特定上下文的REPL,然后用這個(gè)REPL再去干具體的事。
  • 接著,我們基于這個(gè)方案做了任務(wù)分解,粗略分成了做一個(gè)REPL和實(shí)現(xiàn)具體的抓取代碼兩部分。
  • 接著我們靠ChatGPT把些任務(wù)實(shí)現(xiàn),在實(shí)現(xiàn)的過(guò)程中,我們發(fā)現(xiàn)自己對(duì)問(wèn)題域的細(xì)節(jié)了解不夠,于是我們又迭代了我們的任務(wù)列表??梢哉f(shuō)方案沒(méi)有大的變化,實(shí)現(xiàn)上做了很多調(diào)整。

最終,我們就靠ChatGPT把這個(gè)REPL給做了出來(lái),為了寫(xiě)一個(gè)這樣的小功能,我們做了個(gè)框架,頗有點(diǎn)為了這點(diǎn)醋才包的這頓餃子的味道了。這要是在以前的時(shí)代,是一個(gè)巨大的浪費(fèi),但其實(shí)先做一個(gè)框架的思路在ChatGPT時(shí)代應(yīng)該成為一種習(xí)慣,它會(huì)從兩個(gè)方面帶來(lái)好處:

  • 可以降低輸入的文本數(shù)量,避免ChatGPT犯錯(cuò)。因?yàn)楹芏嗳硕贾?,ChatGPT可以快速寫(xiě)出一些小程序,但是長(zhǎng)一點(diǎn)的總是會(huì)出錯(cuò),很多人到這里就放棄了,但其實(shí),我們會(huì)發(fā)現(xiàn)如果我們能把問(wèn)題分解到它恰好擅長(zhǎng)的領(lǐng)域我們就可以最大限度的利用它的優(yōu)勢(shì),規(guī)避它的劣勢(shì)。人類(lèi)歷史上,蒸汽機(jī)車(chē)發(fā)明的時(shí)候,它肯定不如馬耐顛,但為了充分理由他的優(yōu)勢(shì),人們?yōu)樗伭髓F軌。直到今天為了發(fā)揮機(jī)動(dòng)車(chē)的效力,我們還是要修路鋪軌,但是我們并不覺(jué)得有什么不對(duì),從這個(gè)角度來(lái)講,我們也不該只盯著ChatGPT的缺點(diǎn)看,揚(yáng)長(zhǎng)避短才是正道。
  • 縮短反饋環(huán),提高效率。從整體效率角度來(lái)講,只有反饋環(huán)的縮短才是真正提高了效率,某一步的快速完成并不真正提高效率。所謂反饋環(huán)的縮短在我們的上下文里就是”我想到怎么編碼完成任務(wù) -> 編碼 -> 測(cè)試 -> 得知代碼執(zhí)行失敗->我又想到怎么編碼完成任務(wù)"的這個(gè)循環(huán),我們不能假設(shè)代碼編寫(xiě)一次成功,所以這個(gè)環(huán)越短,我們的效率就越高。在這個(gè)例子里我想到了我不能一次寫(xiě)對(duì),所以我就先做了REPL,這就是所謂磨刀不誤砍柴工。但是道理大家都懂,在有ChatGPT之前,磨刀這個(gè)事他總是誤砍柴工的,但是在今天,你可以用幾個(gè)問(wèn)題就得到一個(gè)趁手的工具,開(kāi)始你的工作,所以不要著急沖進(jìn)去工作,先做個(gè)工具可能是新時(shí)代的好習(xí)慣。

下一篇,我們將進(jìn)入這樣一個(gè)場(chǎng)景:我基于這個(gè)框架,我寫(xiě)了很多爬蟲(chóng)代碼,我該怎么組織和管理這些代碼呢?我需不需要一個(gè)精妙設(shè)計(jì)的內(nèi)部框架和規(guī)范來(lái)組織我的代碼呢?

相關(guān)文章

  • 深入理解Node.js 事件循環(huán)和回調(diào)函數(shù)

    深入理解Node.js 事件循環(huán)和回調(diào)函數(shù)

    這篇文章主要介紹了深入理解Node.js 事件循環(huán)和回調(diào)函數(shù),詳細(xì)的介紹Node.js 事件循環(huán)和Node.js回調(diào)函數(shù),需要學(xué)習(xí)的可以參考一下。
    2016-11-11
  • node.js將MongoDB數(shù)據(jù)同步到MySQL的步驟

    node.js將MongoDB數(shù)據(jù)同步到MySQL的步驟

    這篇文章主要給大家介紹了關(guān)于node.js將MongoDB數(shù)據(jù)同步到MySQL的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-12-12
  • Nodejs 發(fā)布自己的npm包并制作成命令行工具的實(shí)例講解

    Nodejs 發(fā)布自己的npm包并制作成命令行工具的實(shí)例講解

    今天小編就為大家分享一篇Nodejs 發(fā)布自己的npm包并制作成命令行工具的實(shí)例講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-05-05
  • 用Nodejs實(shí)現(xiàn)在終端中炒股的實(shí)現(xiàn)

    用Nodejs實(shí)現(xiàn)在終端中炒股的實(shí)現(xiàn)

    這篇文章主要介紹了用Nodejs實(shí)現(xiàn)在終端中炒股的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-10-10
  • nodejs實(shí)現(xiàn)的http、https 請(qǐng)求封裝操作示例

    nodejs實(shí)現(xiàn)的http、https 請(qǐng)求封裝操作示例

    這篇文章主要介紹了nodejs實(shí)現(xiàn)的http、https 請(qǐng)求封裝操作,結(jié)合實(shí)例形式分析了node.js針對(duì)http、https 請(qǐng)求的封裝與使用相關(guān)操作技巧,需要的朋友可以參考下
    2020-02-02
  • 初始Nodejs

    初始Nodejs

    本文屬于nodejs的基礎(chǔ)知識(shí)介紹篇,從nodejs的基本概念,到架構(gòu)示意圖,同步異步等方面做了詳細(xì)的解答,是篇非常不錯(cuò)的文章
    2014-11-11
  • node.js中的fs.ftruncate方法使用說(shuō)明

    node.js中的fs.ftruncate方法使用說(shuō)明

    這篇文章主要介紹了node.js中的fs.ftruncate方法使用說(shuō)明,本文介紹了fs.ftruncate的方法說(shuō)明、語(yǔ)法、接收參數(shù)、使用實(shí)例和實(shí)現(xiàn)源碼,需要的朋友可以參考下
    2014-12-12
  • Node.js的MongoDB驅(qū)動(dòng)Mongoose基本使用教程

    Node.js的MongoDB驅(qū)動(dòng)Mongoose基本使用教程

    這篇文章主要介紹了Node.js的MongoDB驅(qū)動(dòng)Mongoose的基本使用教程,前端js+后端Node.js+數(shù)據(jù)庫(kù)MongoDB是當(dāng)下流行的JavaScript全棧開(kāi)發(fā)方案,需要的朋友可以參考下
    2016-03-03
  • node.js中Util模塊作用教程示例詳解

    node.js中Util模塊作用教程示例詳解

    這篇文章主要為大家介紹了node.js中Util模塊的教程示例詳解,帶大家充分的了解node.js的util模塊,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2021-11-11
  • Node.JS中快速掃描端口并發(fā)現(xiàn)局域網(wǎng)內(nèi)的Web服務(wù)器地址(80)

    Node.JS中快速掃描端口并發(fā)現(xiàn)局域網(wǎng)內(nèi)的Web服務(wù)器地址(80)

    在 Node.JS 中進(jìn)行端口掃描還是比較方便的,一般會(huì)有廣播和輪詢(xún)兩種方式。下文重點(diǎn)給大家介紹node.js 掃描端口并發(fā)現(xiàn)局域網(wǎng)內(nèi)的web服務(wù)器地址的方法,一起看看吧
    2017-09-09

最新評(píng)論