百度,搜狐,360等搜索引擎;
boost的官网是没有站内搜索的。
爬虫程序我就不做了,受国家的法律法规的限制,我就通过正规的下载途径来做。
目标文档进行分词(目的:方便建立倒排索引和查找):
关键字(具有唯一性) | 文档,weight(权重) |
雷军 | 文档1,文档2 |
买 | 文档1 |
四斤 | 文档1 |
小米 | 文档1,文档2 |
四斤小米 | 文档1 |
发布 | 文档2 |
小米手机 | 文档2 |
模拟一次查找的过程:
- #include
- #include
- #include
- #include
- #include"util.hpp"
-
-
- const std::string src_path = "data/input/"; //所有的html
- const std::string output = "data/raw_html/raw.txt"; //解析所有完的html
-
- typedef struct DocInfo54
- {
- std::string title; //文档的标题
- std::string content; //文档的内容
- std::string url; //该文档在官网的url
- }DocInfo_t;
-
- bool EnumFile(const std::string &src_path,std::vector
*file_list) ; - bool ParseHtml(std::vector
& file_list,std::vector *results) ; - bool SaveHtml(std::vector
& results,const std::string &output) ; -
-
-
- static bool ParseTitle(const std::string &result,std::string *title)
- {
- size_t begin = result.find("
" ); - if(begin == std::string::npos)
- {
- return false;
- }
- size_t end = result.find("");
- if(end == std::string::npos)
- {
- return false;
- }
- begin += std::string("
" ).size(); - if(begin > end)
- {
- return false;
- }
- *title = result.substr(begin,end - begin);
-
- return true;
- }
- static bool ParseContent(const std::string &file,std::string *content)
- {
- //去标签,基于一个简单的状态机
- enum status
- {
- LABLE,
- CONTENT
- };
- enum status s = LABLE;
- for(auto e :file)
- {
- switch (s)
- {
- case LABLE:
- if(e == '>') //代表结束
- s = CONTENT;
- /* code */
- break;
- case CONTENT:
- if(e == '<') //代表开始
- s = LABLE;
- else
- {
- if(e == '\n') e = ' ';
- *content += e;
- }
- break;
- default:
- break;
- }
- }
- return true;
- }
- static bool ParseUrl(const std::string &file,std::string *url)
- {
- std::string url_head = "https://www.boost.org/doc/libs/1_79_0/doc/html/";
- std::string url_tail = file.substr(src_path.size());
- *url = url_head + url_tail;
- return true;
- }
-
- int main()
- {
- //第一步拿到所有文件名
- std::vector
files_list; - if(!EnumFile(src_path,&files_list))
- {
- std::cerr<<"enum file name error"<
- return 1;
- }
- //第二步解析文件
- std::vector
results; - if(!ParseHtml(files_list,&results))
- {
- std::cerr<<"parse is error"<
- return 2;
- }
- //第三步,把解析完毕的各个文件内容,写入output,按照\3作为每个文档的分隔符
- if(!SaveHtml(results,output))
- {
- std::cerr<<"save html error"<
- return 3;
- }
- return 0;
- }
-
- bool EnumFile(const std::string &src_path,std::vector
*file_list) //拿到所有html文件名 - {
- namespace fs = boost::filesystem;
- fs::path root_path(src_path); //创建一个路径名对象
- if(!fs::exists(root_path)) //根据路径创建的对象不存在
- {
- std::cerr<
"not exists"< - return false;
- }
- //定义一个空迭代器,用来判断递归结束
- fs::recursive_directory_iterator end;
- for(fs::recursive_directory_iterator it(root_path); it != end; ++it)
- {
- if(!fs::is_regular_file(*it)) //如果不是普通文件继续
- {
- continue;
- }
- if(it->path().extension() != ".html")
- {
- continue;
- }
- //测试
- //std::cout<<"debug"<
path().string()< - file_list->push_back(it->path().string());
- }
-
- return true;
- }
- void ShowInfo(const DocInfo_t &doc)
- {
- std::cout<
- std::cout<
- std::cout<
- }
- bool ParseHtml(std::vector
& file_list,std::vector *results) //拿到所有html的标题,内容,url - {
- for(const auto file : file_list)
- {
- //1读取文件
- std::string result;
- if(!ns_util::FileUtil::ReadFile(file,&result))
- {
- //文件读取失败
- continue;
- }
- DocInfo_t doc;
- //2提取标签
- if(!ParseTitle(result,&doc.title))
- {
- continue;;
- }
- //3提取内容
- if(!ParseContent(result,&doc.content))
- {
- continue;
- }
- //4提取url
- if(!ParseUrl(file,&doc.url))
- {
- continue;
- }
- //将结果出入到vector,这里有拷贝问题,以后在优化
- results->push_back(std::move(doc)); //采用右值,资源转移
- //for debug
- //ShowInfo(doc);
- //break;
- }
- return true;
- }
- bool SaveHtml(std::vector
& results,const std::string &output) - {
- #define SEP '\3'
- std::ofstream of(output,std::ios::out | std::ios::binary);
- if(!of.is_open())
- {
- std::cerr<<"open"<
- return false;
- }
- //写入文件
- for(const auto &item : results)
- {
- std::string out_result;
- out_result = item.title;
- out_result += SEP;
- out_result += item.content;
- out_result += SEP;
- out_result += item.url;
- out_result += '\n';
-
- of.write(out_result.c_str(),out_result.size());
- }
- of.close();
- return true;
- }
boost 开发库的安装
s
udo yum install
-
y boost
-
devel
//
是
boost
开发库
提取
title
在进行遍历的时候,只要碰到了 > ,就意味着,当前的标签被处理完毕. 只要碰到了 < 意味着新的标签开始了
构建URL
boost
库的官方文档,和我们下载下来的文档,是有路径的对应关系的
官网
URL
样例:
https://www.boost.org/doc/libs/1_78_0/doc/html/accumulators.html
我们下载下来的
url
样例:
boost_1_78_0/doc/html/accumulators.html
我们拷贝到我们项目中的样例:
data/input/accumulators.html //
我们把下载下来的
boost
库
doc/html/* copy
data/input/
url_head = "https://www.boost.org/doc/libs/1_78_0/doc/html";
url_tail = [data/input](
删除
) /accumulators.html -> url_tail = /accumulators.html
url = url_head + url_tail ;
相当于形成了一个官网链接
将解析内容写入文件中
//
见代码
采用下面的方案:
version2
:
写入文件中,一定要考虑下一次在读取的时候,也要方便操作
!
类似:
title\3content\3url \n title\3content\3url \n title\3content\3url \n
...
方便我们
getline
(
ifsream
,
line
)
,直接获取文档的全部内容:
title\3content\3url
6.编写建立索引的模块Index
- #pragma once
- #include
- #include
- #include
- #include
- #include
- #include "util.hpp"
- #include
- #include"log.hpp"
- namespace ns_index
- {
- struct DocInfo
- {
- std::string title; //文档标题
- std::string content; //文档对应的去标签之后的内容
- std::string url; //官网的url
- uint64_t doc_id; //文档的id
- };
-
- struct InvertedElem
- {
- uint64_t doc_id;
- std::string word;
- int weigth;
- };
-
- //倒排拉链
- typedef std::vector
InvertedList; -
- class Index
- {
- private:
- std::vector
forward_index; //正排索引 - std::unordered_map
inverted_index; //倒排索引 - static Index *Instance;
- static std::mutex mtx;
-
- private:
- Index() = default;
- Index(const Index &) = delete;
- Index &operator=(const Index &) = delete;
-
- public:
- ~Index() = default;
- static Index *GetInstance()
- {
- if (nullptr == Instance)
- {
- mtx.lock();
- if (nullptr == Instance)
- {
- Instance = new Index();
- }
- mtx.unlock();
- }
-
- return Instance;
- }
- //根据doc_id找到文档内容
- DocInfo *GetForWardIndex(uint64_t doc_id)
- {
- if (doc_id >= forward_index.size())
- {
- std::cerr << "doc_id is error" << std::endl;
- return nullptr;
- }
- return &forward_index[doc_id];
- }
- //根据关键字string,获得倒排拉链
- InvertedList *GetInvertedList(const std::string &word)
- {
- auto it = inverted_index.find(word);
- if (it == inverted_index.end())
- {
- std::cerr << word << "have no InvertedList" << std::endl;
- return nullptr;
- }
- return &(it->second);
- }
- //根据去标签,格式化之后的文档,构建正排索引和倒排索引
- // data/raw_html/raw.txt
- bool BuildIndex(const std::string &input)
- {
- std::ifstream in(input, std::ios::in | std::ios::binary);
- if (!in.is_open())
- {
- std::cerr << "sorry" << input << "open sorry" << std::endl;
- return false;
- }
- std::string line;
-
- int count = 0;
- while (std::getline(in, line))
- {
- DocInfo *doc = BuildForwardIndex(line); //构建正排
- if (doc == nullptr)
- {
- std::cerr << "build" << line << std::endl; // for debug
- continue;
- }
- BuildInvertedIndex(*doc);
- count++;
- if(count % 50 == 0) //std::cout<<"当前已经建立的索引文档:"<
- LOG(NORMAL, "当前的已经建立的索引文档: " + std::to_string(count));
- }
- return true;
- }
-
- private:
- DocInfo *BuildForwardIndex(const std::string &line)
- {
- // 1进行字符串切分
- std::vector
results; - const std::string seq = "\3";
- ns_util::StringUtil::Split(line, &results, seq);
- if (results.size() != 3)
- {
- return nullptr;
- }
- // 2将字符串进行填充到DocInfo
- DocInfo doc;
- doc.title = results[0];
- doc.content = results[1];
- doc.url = results[2];
- doc.doc_id = forward_index.size();
- // 3插入到正排索引的vector中
- forward_index.push_back(std::move(doc));
- return &forward_index.back();
- }
- bool BuildInvertedIndex(const DocInfo &doc)
- {
- // word 倒排拉链
- struct word_cnt
- {
- /* data */
- int title_cnt;
- int content_cnt;
- word_cnt() : title_cnt(0), content_cnt(0) {}
- };
- std::unordered_map
word_map; - //对标题进行分词
- std::vector
title_words; - ns_util::JiebaUtil::CurString(doc.title, &title_words);
- for (auto s : title_words)
- {
- boost::to_lower(s); //转化成小写
- word_map[s].title_cnt++;
- }
- //对文档内容进行分词
- std::vector
contnet_word; - ns_util::JiebaUtil::CurString(doc.content, &contnet_word);
- for (auto s : contnet_word)
- {
- boost::to_lower(s); //转化成小写
- word_map[s].content_cnt++;
- }
- #define X 10
- #define Y 1
- for (auto &word_pair : word_map)
- {
- InvertedElem item;
- item.doc_id = doc.doc_id;
- item.word = word_pair.first;
- //相关性
- item.weigth = X * word_pair.second.title_cnt + Y * word_pair.second.content_cnt;
- InvertedList &inverted_list = inverted_index[word_pair.first];
- inverted_list.push_back(std::move(item));
- }
- return true;
- }
- };
- Index* Index::Instance = nullptr;
- std::mutex Index::mtx;
- }
//jieba
的使用
--cppjieba
获取链接:
git clone https
:
//gitcode.net/mirrors/yanyiwu/cppjieba.git
如何使用:注意细节,我们需要自己执行:
cd cppjieba
;
cp
-
rf deps
/
limonp include
/
cppjieba
/
,
不然会编 译报错
7.编写搜索引擎模块Searcher
- #pragma once
- #include "index.hpp"
- #include
- #include
- // struct Com
- // {
- // bool operator>(const InvertedElem& e1,const InvertedElem& e2)
- // {
- // return e1.weigth > e2.weigth;
- // }
- // }
- struct InvertedElemPrint
- {
- uint64_t doc_id;
- int weight;
- std::vector
words; - InvertedElemPrint() : doc_id(0), weight(0) {}
- };
- namespace ns_searcher
- {
- class Searcher
- {
- private:
- ns_index::Index *index;
-
- public:
- Searcher() = default;
- ~Searcher() = default;
- void InitSearcher(const std::string &input)
- {
- // 1.获取或者创建index对象
- index = ns_index::Index::GetInstance(); //获得单例
- //std::cout << "获取单例成功" << std::endl;
- LOG(NORMAL, "获取index单例成功...");
- // 2.根据index对象建立索引
- index->BuildIndex(input);
- // std::cout << "建立正排和倒排索引成功...." << std::endl;
- LOG(NORMAL, "建立正排和倒排索引成功...");
- }
-
- std::string GetDesc(const std::string &html_src, const std::string &word)
- {
- const int prev_step = 50;
- const int next_step = 100;
- //找到首次出现的位置
- // std::size_t pos = html_src.find(word); //错误原文档没有忽略大小写
- auto it = std::search(html_src.begin(), html_src.end(), word.begin(), word.end(),
- [](int a, int b)
- { return std::tolower(a) == std::tolower(b); });
- int pos = std::distance(html_src.begin(), it);
- if (pos == std::string::npos)
- {
- return "None1"; //不存在这种情况
- }
- // 2获取start end
- int start = 0;
- int end = html_src.size() - 1;
-
- if (pos > start + prev_step)
- start = pos - prev_step;
- if (pos < end - next_step)
- end = pos + next_step;
-
- if (start >= end)
- return "None2";
- return html_src.substr(start, end - start) + "...";
- }
- // query:搜索关键字
- // josn_string:返回给用户的搜索结果
- void Search(const std::string &query, std::string *json_string)
- {
- // 1.[分词]:对我们的query进行按照searcher的要求进行分词
- std::vector
words; - ns_util::JiebaUtil::CurString(query, &words);
- // 2.[触发]:就是根据分词的各个“词”,进行Index查找
- // ns_index::InvertedList inverted_list_all;
- std::vector
inverted_list_all; -
- std::unordered_map<uint64_t, InvertedElemPrint> tokens_map;
- for (auto &e : words)
- {
- boost::to_lower(e);
- ns_index::InvertedList *inverted_list = index->GetInvertedList(e);
- if (inverted_list == nullptr)
- continue;
- //不完美的地方,可能有重复的文档
- // inverted_list_all.insert(inverted_list_all.end(),inverted_list->begin(),inverted_list->end());
- for (const auto &elem : *inverted_list)
- {
- auto &item = tokens_map[elem.doc_id]; //[]:如果存在直接获取,如果不存在新建
- // item一定是doc_id相同的print节点
- item.doc_id = elem.doc_id;
- item.weight += elem.weigth;
- item.words.push_back(elem.word);
- }
- for (const auto &elem : *inverted_list)
- {
- auto &item = tokens_map[elem.doc_id]; //[]:如果存在直接获取,如果不存在新建
- // item一定是doc_id相同的print节点
- item.doc_id = elem.doc_id;
- item.weight += elem.weigth;
- item.words.push_back(elem.word);
- }
- for (const auto &item : tokens_map)
- {
- inverted_list_all.push_back(std::move(item.second));
- }
- }
- // 3.[合并排序]:汇总查找结果,按照相关性(weight)降序排序
- // std::sort(inve rted_list_all.begin(), inverted_list_all.end(),\
- // []( const ns_index::InvertedElem e1, const ns_index::InvertedElem e2){
- // return e1.weigth > e2.weigth;
- // });
- // std::sort(inverted_list_all.begin(),inverted_list_all.end(),Com());
-
- std::sort(inverted_list_all.begin(), inverted_list_all.end(),
- [](const InvertedElemPrint &e1, const InvertedElemPrint &e2)
- {
- return e1.weight > e2.weight;
- });
- // 4.[构建]:根据查找出来的结果,构建jsonc串 -----jsoncpp
- Json::Value root;
- for (auto &item : inverted_list_all)
- {
- ns_index::DocInfo *doc = index->GetForWardIndex(item.doc_id);
- if (doc == nullptr)
- continue;
-
- Json::Value elem;
- elem["title"] = doc->title;
- elem["desc"] = GetDesc(doc->content, item.words[0]); // content是文档的去标签的结果,但是不是我们想要的,我们要的是一部分 TODO
- elem["url"] = doc->url;
- // for deubg, for delete
- elem["id"] = (int)item.doc_id;
- elem["weight"] = item.weight; // int->string
-
- root.append(elem);
- }
-
- Json::StyledWriter writer;
- *json_string = writer.write(root);
- }
- };
- }
搜索:雷军小米 -> 雷军、小米->查倒排->两个倒排拉链(文档1,文档2,文档1、文档2)
安装 jsoncpp
sudo yum install
-
y jsoncpp
-
devel
获取摘要
关于调试
把整个文件读到内存
先拿到标题,取到了标题。
对整个文件进行去标签,其中是包括标签的!!!!
实际如果一个词在
title
中出现,一定会被当标题 和 当内容分别被统计一次!!!
8.编写http server模块
cpp
-
httplib
库:
https
:
//gitee.com/zhangkt1995/cpp-httplib?_from=gitee_search
注意:
cpp
-
httplib
在使用的时候需要使用较新版本的
gcc
,
centos
7
下默认
gcc
4.8.5
sudo yum install centos
-
release
-
scl scl
-
utils
-
build
yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils # 安装gcc和g++
scl enable devtoolset
-
7
bash
ls
/
opt
/
rh
/
//
启动: 细节,命令行启动只能在本会话有效
//
可选:如果想每次登陆的时候,都是较新的
gcc
永久更新 vim ~
/
.
bash_profile 在文本末尾添加 scl enable devtoolset
-
7
bash
安装
cpp
-
httplib
最新的
cpp
-
httplib
在使用的时候,如果
gcc
不是特别新的话有可能会有运行时错误的问题
建议:
cpp
-
httplib
0.7.15
下载
zip
安装包,上传到服务器即可 或者直接get clone也行,在gitee上有项目,直接搜索cpp-hpplib
- #include "cpp-httplib/httplib.h"
- #include "searcher.hpp"
- const std::string root_path = "./wwwroot";
- const std::string input ="data/raw_html/raw.txt";
- int main()
- {
- ns_searcher::Searcher searcher;
- searcher.InitSearcher(input);
- httplib::Server svr;
- svr.set_base_dir(root_path.c_str());
- svr.Get("/s", [&searcher](const httplib::Request &req, httplib::Response &rsp){
- if(!req.has_param("word"))
- {
- rsp.set_content("必须要有搜索关键字!","text/plain: chatset=utf-8");
- return;
- }
- std::string word = req.get_param_value("word");
- // std::cout<<"用户正在搜索:"<
- LOG(NORMAL, "用户搜索的: " + word);
- std::string json_string;
- searcher.Search(word,&json_string);
- rsp.set_content(json_string,"application/json");
- });
- //rsp.set_content("你好,世界!", "text/plain; charset=utf-8");
- LOG(NORMAL, "服务器启动成功...");
- svr.listen("0.0.0.0", 8081); return 0;
- }
9.编写前段模块
- html>
- <html lang="en">
-
- <head>
- <meta charset="UTF-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <script src="http://code.jquery.com/jquery-2.1.1.min.js">script>
- <title>boost 搜索引擎title>
- <style>
- /* 去掉网页中的所有的默认内外边距,html的盒子模型 */
- * {
- /* 设置外边距 */
- margin: 0;
- /* 设置内边距 */
- padding: 0;
- }
-
- /* 将我们的body内的内容100%和html的呈现吻合 */
- html,
- body {
- height: 100%;
- }
-
- /* 类选择器.container */
- .container {
- /* 设置div的宽度 */
- width: 800px;
- /* 通过设置外边距达到居中对齐的目的 */
- margin: 0px auto;
- /* 设置外边距的上边距,保持元素和网页的上部距离 */
- margin-top: 15px;
- }
-
- /* 复合选择器,选中container 下的 search */
- .container .search {
- /* 宽度与父标签保持一致 */
- width: 100%;
- /* 高度设置为52px */
- height: 52px;
- }
-
- /* 先选中input标签, 直接设置标签的属性,先要选中, input:标签选择器*/
- /* input在进行高度设置的时候,没有考虑边框的问题 */
- .container .search input {
- /* 设置left浮动 */
- float: left;
- width: 600px;
- height: 50px;
- /* 设置边框属性:边框的宽度,样式,颜色 */
- border: 1px solid black;
- /* 去掉input输入框的有边框 */
- border-right: none;
- /* 设置内边距,默认文字不要和左侧边框紧挨着 */
- padding-left: 10px;
- /* 设置input内部的字体的颜色和样式 */
- color: #CCC;
- font-size: 15px;
- }
-
- /* 先选中button标签, 直接设置标签的属性,先要选中, button:标签选择器*/
- .container .search button {
-
- /* 设置left浮动 */
- float: left;
- width: 150px;
- height: 52px;
- /* 设置button的背景颜色,#4e6ef2 */
- background-color: #4e6ef2;
- /* 设置button中的字体颜色 */
- color: #FFF;
- /* 设置字体的大小 */
- font-size: 19px;
- font-family: Georgia, 'Times New Roman', Times, serif;
- }
-
- .container .result {
- width: 100%;
- }
-
- .container .result .item {
- margin-top: 15px;
- }
-
- .container .result .item a {
- /* 设置为块级元素,单独站一行 */
- display: block;
- /* a标签的下划线去掉 */
- text-decoration: none;
- /* 设置a标签中的文字的字体大小 */
- font-size: 20px;
- /* 设置字体的颜色 */
- color: #4e6ef2;
- }
-
- .container .result .item a:hover {
- /*设置鼠标放在a之上的动态效果*/
- text-decoration: underline;
- }
-
- .container .result .item p {
- margin-top: 5px;
- font-size: 16px;
- font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
- }
-
- .container .result .item i {
- /* 设置为块级元素,单独站一行 */
- display: block;
- /* 取消斜体风格 */
- font-style: normal;
- color: green;
- }
- style>
- head>
-
- <body>
- <div class="container">
- <div class="search"> <input type="text" value="输入搜索关键字...">
- <button onclick="Search()">搜索一下button>
- div>
- <div class="result">
-
- div>
- div>
- <script>
- function Search() { // 是浏览器的一个弹出框
- // alert("hello js!");
- //1. 提取数据, $可以理解成就是JQuery的别称
- let query = $(".container .search input").val();
- console.log("query = " + query);
- //console是浏览器的对话框,可以用来进行查看js数据
- //2. 发起http请求,ajax: 属于一个和后端进行数据交互的函数,JQuery中的
- $.ajax({
- type: "GET",
- url: "/s?word=" + query,
- success: function (data) {
- console.log(data);
- BuildHtml(data);
- }
- });
- }
- function BuildHtml(data) {
- // 获取html中的result标签
- let result_lable = $(".container .result");
- // 清空历史搜索结果
- result_lable.empty();
- for (let elem of data) {
- // console.log(elem.title);
- // console.log(elem.url);
- let a_lable = $("", {
- text: elem.title,
- href: elem.url,
- // 跳转到新的页面
- target: "_blank"
- });
- let p_lable = $("
"
, { - text: elem.desc
- });
- let i_lable = $("", {
- text: elem.url
- });
- let div_lable = $("", {
- class: "item"
- });
- a_lable.appendTo(div_lable);
- p_lable.appendTo(div_lable);
- i_lable.appendTo(div_lable);
- div_lable.appendTo(result_lable);
- }
- }
- script>
- body>
-
- html>
makefile
- Parser=parser
- DUG=debug
- HTTP_SEARCHER=http_searcher
- cc=g++
-
- .PHONY:all
- all:$(Parser) $(DUG) $(HTTP_SEARCHER)
- $(Parser):parser.cc
- $(cc) -o $@ $^ -lboost_system -lboost_filesystem -std=c++11
- $(DUG):debug.cc
- $(cc) -o $@ $^ -ljsoncpp -std=c++11
- $(HTTP_SEARCHER):http_searcher.cc
- $(cc) -o $@ $^ -ljsoncpp -lpthread -std=c++11
-
-
- .PHONY:clean
- clean:
- rm -rf $(Parser) $(DUG) $(HTTP_SEARCHER)
10.项目效果
11.添加日志
- #pragma once
-
- #include
- #include
- #include
-
- #define NORMAL 1
- #define WARNING 2
- #define DEBUG 3
- #define FATAL 4
-
- #define LOG(LEVEL, MESSAGE) log(#LEVEL, MESSAGE, __FILE__, __LINE__)
-
- void log(std::string level, std::string message, std::string file, int line)
- {
- std::cout << "[" << level << "]" << "[" << time(nullptr) << "]" << "[" << message << "]" << "[" << file << " : " << line << "]" << std::endl;
- }
结项总结
项目扩展方向
1.
建立整站搜索
2.
设计一个在线更新的方案,信号,爬虫,完成整个服务器的设计
3.
不使用组件,而是自己设计一下对应的各种方案(有时间,有精力)
4.
在我们的搜索引擎中,添加竞价排名
(
强烈推荐
)
5.
热次统计,智能显示搜索关键词(字典树,优先级队列)
(
比较推荐
)
6.
设置登陆注册,引入对
mysql
的使用
(
比较推荐的
)
-
相关阅读:
剑指 Offer 14- I. 剪绳子
spring+vue项目搭建
Python爬虫提高排名
设计模式6、适配器模式 Adapter
app小程序手机端Python爬虫实战02-uiautomator2自动化抓取开发环境搭建
如何将网站部署到浏览器?
CoreData 在新建或更新托管对象中途发生错误时如何恢复如初?
网络编程 tcp/ip下的c/s模型介绍
让reviewdog支持gitlab-push-commit,守住代码质量下限
每日学到 40
-
原文地址:https://blog.csdn.net/qq_57283958/article/details/125946310