【项目】Boost搜索引擎

项目相关背景

现在市面上已经出现很多搜索引擎,比如:百度、Google、Bing等等,它们都是全网性搜索
而我做得项目就像cplusplus网站中搜索C++的相关知识一样,同样做的是站内搜索,它的搜索更垂直。


搜索引擎的宏观原理

在这里插入图片描述
server是我们所对应的服务器,在服务器上一定运行的有服务软件searcher,做项目之前首先我们需要准备好数据,也就是相关的html,我们将下载好的html保存到磁盘上面的data/input路径下,接下来就是需要做1. 去标签&&数据清理 2.建立索引(索引为了让搜索的速度更加快)

接下来,用户通过浏览器以HTTP请求GET的方式上传搜索关键字,发起一次搜索任务,服务软件检索索引,查找相关的html,将拼接后的html返回用户


搜索引擎技术栈和项目环境

技术栈:C/C++ C++11,STL, 准标准库Boost,Jsoncpp, cppjieba, cpp-httplib,  html5, css, js,  jQuery, Ajax
项目环境:Centos7云服务器 vim/gcc(g++)/Makefile vs2022 vscode

正排索引 VS 倒排索引 - 搜索引擎具体原理:

1. 正排索引就是从文档ID找到文档的内容(文档中的关键字)
  • 文档1:大学生需要考试
  • 文档2:大学生开始周开始了
文档ID文档内容
1大学生需要考试
2大学生开始周开始了
2. 目标文档进行分词

目的:方便建立倒排索引和查找
文档1 -> 大学生需要考试:大学生/ 需要 / 考试
文档2 -> 大学生考试周开始了:大学生/ 开始 / 考试周

3. 倒排索引:根据文档内容,分词,整理不重复的各个关键字,对应联系到文档ID的方案
关键字文档ID
大学生文档1, 文档2
需要文档1
考试文档1
开始文档2
考试周文档2

模拟一次查找的过程:
用户输入: 大学生 -> 倒排索引中查找 -> 提取出文档ID(1,2) -> 根据正排索引 -> 找到正排索引 -> 找到文档内容 -> 构建响应结果


编写数据去标签与数据清洗的模块 Parser

1.准备数据

boost 官网: https://www.boost.org/
我们先创建一个项目文件夹mkdir boost_searcher,在boost官网中下载网页
在这里插入图片描述
选择最新的版本
在这里插入图片描述
下载的本地磁盘后,我们将这个压缩包上传到我们刚刚在云服务器上创建的项目文件夹中这里我们使用 rz 命令将本地的文件上传到服务器上

注意:如果上传的速度很慢,我们可以打开Windows中的cmd,使用scp协议上传到云服务器指定的路径下:

scp /path/to/localfile username@server_ip:/path/to/destination

  • /path/to/localfile 是本地文件的路径和文件名。
  • username 是你在服务器上的用户名。
  • server_ip 是你的服务器的IP地址。
  • /path/to/destination 是文件在服务器上的目标路径和文件名。

我们点开boost库 中的一个手册,通过URL我们发现https://www.boost.org/doc/libs/1_84_0/doc/html/array.html它的手册都是在doc/html这个路径下的

我们将这个路径下的所有htm拷贝到项目目录下的data/input目录下,使用这些html建立索引

mkdir -p data/input
cp -rf boost_1_84_0/doc/html.* data/input
2.去标签
//创建去标签的源代码文件
touch parser.cc

在这里插入图片描述

<> : html的标签,这个标签对我们进行搜索是没有价值的,需要去掉这些标签,一般标签都是成对出现的!

在这里插入图片描述

ls -Rl | grep -E '*.html' | wc -l  //查看一共有多少个.html文件

在这里插入图片描述
目标:把每一个文档都去标签,然后写入到同一个文件当中!每一个文档的内容只占一行,文档和文档内容之间使用\3区分
\3在ASCLL表中它是控制字符,是不可显示的,所以不会污染内容

3.编写parser.cc
#include <iostream>
#include <string>
#include <vector>// 这个目录下面放的是所有的html网页
const std::string src_path = "data/input";
const std::string output = "data/raw_html/raw.txt";typedef struct DocInfo
{std::string title;std::string content; //文档内容std::string url;  //表示该 文档在官网中的url
}DocInfo_t;//const & :输入
//*: 输出
//&: 输入输出bool EnumFile(const std::string& src_path, std::vector<std::string>* file_list);
bool ParseHtml(const std::vector<std::string>& file_list, std::vector<DocInfo_t>* results);
bool SaveHtml(const std::vector<DocInfo_t>& results, const std::string& output);int main()
{std::vector<std::string> file_list;// 第一步:递归式的显示每一个html文件名带路径,保存到files_list 方便后期对一个个文件进行读取if (!EnumFile(src_path, &file_list)){std::cerr << "enum file name error!" << std::endl;return 1;}//第二步:按照file_list读取每个文件的内容,并解析std::vector<DocInfo_t> results;if(!ParseHtml(file_list, &results)){std::cerr << "Parse html error" << std::endl;return 2;}//第三步:把解析完毕的文件内容,写入到output文件中 按照\3作为每个html的分隔符if(!SaveHtml(results, output)){std::cerr << "Save Html error" << std::endl;return 3;}return 0;
}bool EnumFile(const std::string& src_path, std::vector<std::string>* file_list)
{}
bool ParseHtml(const std::vector<std::string>& file_list, std::vector<DocInfo_t>* results)
{}
bool SaveHtml(const std::vector<DocInfo_t>& results, const std::string& output)
{}
4.服务器上安装boost库
//安装boost开发文件和头文件
sudo yum install -y boost-devel

Boost库是一个由C++编写的开源软件库,它提供了许多功能丰富的模块,涵盖了从数据结构到多线程编程等各个领域。Boost库的目标是为C++程序员提供一组高质量、可移植、跨平台的工具和组件,以增强C++语言的功能和性能。Boost库中的许多组件在C++标准化过程中被采纳为标准的一部分,因此它们在C++社区中被广泛使用。

5.完成EnumFile(保存html文件名带路径)
bool EnumFile(const std::string& src_path, std::vector<std::string>* file_list)
{//简化boost命名空间namespace fs = boost::filesystem;fs::path root_path(src_path);//如果文件路径不存在,直接返回falseif(!fs::exists(root_path)){  std::cerr << src_path << "not exit" << std::endl;return false;}//定义一个空的迭代器,用来进行判断递归结束fs::recursive_directory_iterator end;for(fs::recursive_directory_iterator iter(root_path); iter != end; iter++){//首先检查当前的文件是否是一个普通文件 if(!fs::is_regular_file(*iter)){continue;}//提取出文件后缀,判断是否是.html文件if(iter->path().extension() != ".html"){continue;}//走到这里当前的路径一定是合法的,以.html为后缀file_list->push_back(iter->path().string());}
}
6.完成ParseHtml(解析文档内容)
bool ParseHtml(const std::vector<std::string>& file_list, std::vector<DocInfo_t>* results)
{for(const std::string& file : file_list){std::string result;//读取当前路径的文件 将这个html的内容放在result字符串中if(!ns_util::FileUtil::ReadFile(file, &result)){continue;}//解析指定的文件 提取titleDocInfo_t doc;if(!ParseTitle(result, &doc.title)){continue;}//解析文件的contentif(!ParseContent(result, &doc.content)){continue;}//解析文件路径 构建urlif(!ParseUrl(file, &doc.url)){continue;}//当前文档的相关结果保存到了 doc结构里面results->push_back(std::move(doc));//for DebugShowDebug(doc);}return true;
}

自己写的小组件,打开文件然后读取html网页内容

//util.hpp文件
#pragma once
#include<iostream>
#include<string>
#include<fstream>namespace ns_util
{class FileUtil{public:static bool ReadFile(const std::string& file_path, std::string* out){std::ifstream in(file_path, std::ios::in);if(!in.is_open()){std::cerr << "open file " << file_path << "error" << std::endl;return false;}std::string line;while(std::getline(in, line)){*out += line;}in.close();return true;}};
}

解析指定的文件, 提取title

static bool ParseTitle(const std::string& file, std::string* title)
{//解析title,寻找文档中<title></title>标签即可std::size_t begin = file.find("<title>");if(begin == std::string::npos){return false;}std::size_t end = file.find("</title>");if(end == std::string::npos){return false;}//将位置定位到 title的第一个字符begin += std::string("<title>").size();if(begin > end){return false;}//截取title*title =  file.substr(begin, end - begin);return true;
}

解析文件的content

static bool ParseContent(std::string& file, std::string* content)
{//去标签  基于一个简易的状态机编写enum status{LABLE,CONTENT};//一开始的html文件一定是一个标签enum status s = LABLE;for(char c : file){switch(s){case LABLE:if(c == '>')s = CONTENT;break;case CONTENT: if(c == '<')s = LABLE;else {if(c == '\n') c = ' ';content->push_back(c);}break;default:break;}}return true;
}

解析文件路径,构建URL

static bool ParseUrl(const std::string& file_path, std::string* url)
{//官网的搜索路径std::string url_head = "https://www.boost.org/doc/libs/1_85_0/doc/html";//自己本地下载的html保存的路径std::string url_tail = file_path.substr(src_path.size());*url = url_head + url_tail;return true;
}
7.完成SaveHtml(将保存在结构中的html的content保存到文件中)
bool SaveHtml(const std::vector<DocInfo_t>& results, const std::string& output)
{
#define SEP '\3'//将保存在结构中的html的content保存到文件中//title\3content\3url\n  方便我们使用getline函数获取一个html的全部信息//按照二进制的方式写入std::ofstream out(output.c_str(), std::ios::out | std::ios::binary);if(!out.is_open()) {std::cerr << "open " << output << "error" << std::endl;return false;}//进行文件内容的写入for(auto& item : results){std::string out_string;out_string += item.title;out_string += SEP;out_string += item.content;out_string += SEP; out_string += item.url;out_string += '\n';out.write(out_string.c_str(), out_string.size());}out.close();return true;
}

编写建立索引模块 (index)

正排索引我们使用的数据结构是vector,它的vector下标就是文档的ID

倒排索引我们使用的数据结构是Hash表,我们根据文档内容的关键字查找文档拉链

namespace ns_index
{struct DocInfo{std::string title;  //文档的标题std::string content;  //文档对应的去标签之后的内容std::string url; //官网文档的urluint64_t doc_id; //文档的ID};struct InvertedElem{uint64_t doc_id;std::string word;int weight;  //权重};class Index{private://正排索引使用的数据结构是数组,数组的下标是天然的文档IDstd::vector<DocInfo>foward_index;//倒排索引 -》 一个关键字和一组(个)InvertedElem对应std::unordered_map<std::string, std::vector<InvertedElem>> inverted_index;public:Index(){}~Index(){}//根据doc_id 找到文档的内容DocInfo* GetForwardIndex(const uint64_t doc_id){return nullptr;}//根据关键字获得倒排拉链std::vector<InvertedElem>* GetInvertedElemList(const std::string& word){return nullptr;}//根据去标签格式化的文档, 构建正排 倒排索引bool BuildIndex(const std::string& input){return true;}private://建立正派索引DocInfo* BuildForwardIndex(const std::string& line){//解析 line, 字符串切分return nullptr;}//建立倒排索引bool BuildInvertedIndex(const DocInfo& doc){return true;}};
}
1.根据文档的ID查找文档的内容
DocInfo* GetForwardIndex(const uint64_t doc_id)
{if(doc_id >= foward_index.size()){std::cerr << "doc_id out range, error!" << std::endl;return nullptr;}return &foward_index[doc_id];
}
2.根据关键字获得倒排拉链
std::vector<InvertedElem>* GetInvertedElemList(const std::string& word)
{auto iter = inverted_index.find(word);if(iter == inverted_index.end()){std::cerr << word << "not find in unordered_map" << std::endl;return nullptr;}return &(iter->second);
}
3.构建索引(根据去标签的格式化文档构建正排、倒排索引)
//根据去标签格式化的文档, 构建正排 倒排索引
bool BuildIndex(const std::string& input)
{std::ifstream in(input, std::ios::in | std::ios::binary);if(!in.is_open()){std::cerr << input << "open error" << std::endl;return false;}std::string line;while(std::getline(in,line))  //getline函数遇到\n会停止,并且自动丢失,但是当getline读取到文件末尾的时候就会返回false{//这里是将一个清洗之后的Html通过解析 构建正排索引DocInfo* doc = BuildForwardIndex(line);  if(doc == nullptr){std::cerr << "build error" << std::endl;continue;}//将解析完成的结构构倒排索引BuildInvertedIndex(*doc);}return true;
}
4. 构建正排索引
DocInfo* BuildForwardIndex(const std::string& line)
{//解析 line, 字符串切分std::vector<std::string>results;//将html文件解析出来之后 使用sep作为分隔符const std::string sep = "\3";ns_util::StringUtil::CutString(line, &results, sep);if(results.size() != 3){return nullptr;}//字符串填充到DonInfoDocInfo doc;doc.title = results[0];doc.content = results[1];doc.url = results[2];doc.doc_id = foward_index.size();  //对应的ID就是数组下标//插入到正排索引的foward_index中foward_index.push_back(doc);return &doc;
}
5. 构建倒排索引

倒排索引一定是一个关键字和一组(个)InvertedElem对应[关键字和倒排拉链的映射关系]

std::unordered_map<std::string, InvertedList> inverted_index;

struct InvertedElem{ uint64_t doc_id; std::string word; int weight; };
//文档结构:
title : 吃葡萄
content: 吃葡萄不吐葡萄皮
url: http://XXXX
doc_id: 123
根据文档内容,形成一个或者多个InvertedElem(倒排拉链)
因为当前我们是一个一个文档进行处理的,一个文档会包含多个”词“, 都应当对应到当前的doc_id

  1. 需要对 title && content都要先分词 --使用jieba分词
    title: 吃/葡萄/吃葡萄(title_word)
    content:吃/葡萄/不吐/葡萄皮(content_word)
    词和文档的相关性(词频:在标题中出现的词,可以认为相关性更高一些,在内容中出现相关性低一些)

  2. 词频统计

struct word_cnt{
title_cnt;
content_cnt;
}
unordered_map<std::string, word_cnt> word_cnt;
for &word : title_word{
word_cnt[word].title_cnt++; //吃(1)/葡萄(1)/吃葡萄(1)
}
for &word : content_word {
word_cnt[word].content_cnt++; //吃(1)/葡萄(1)/不吐(1)/葡萄皮(1)
}

知道了在文档中,标题和内容每个词出现的次数

  1. 自定义相关性
for &word : word_cnt{
//具体一个词和123文档的对应关系,当有多个不同的词,指向同一个文档的时候,此时该优先显示谁??相关性!
struct InvertedElem elem;
elem.doc_id = 123;
elem.word = word.first;
//相关性,我们这里就简单写了
elem.weight = 10*word.second.title_cnt + word.second.content_cnt ; 
inverted_index[word.first].push_back(elem);
}

获取链接: git clone https://gitcode.net/mirrors/yanyiwu/cppjieba.git
注意细节,我们需要自己执行: cd cppjieba; cp -rf deps/limonp include/cppjieba/, 不然会编译报错

#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <fstream>
#include "util.hpp"
#include <mutex>namespace ns_index
{struct DocInfo{std::string title;   // 文档的标题std::string content; // 文档对应的去标签之后的内容std::string url;     // 官网文档的urluint64_t doc_id;     // 文档的ID};struct InvertedElem{uint64_t doc_id;std::string word;int weight; // 权重};class Index{private:// 正排索引使用的数据结构是数组,数组的下标是天然的文档IDstd::vector<DocInfo> foward_index;// 倒排索引 -》 一个关键字和一组(个)InvertedElem对应std::unordered_map<std::string, std::vector<InvertedElem>> inverted_index;private:Index() {}Index(const Index &) = delete;Index &operator=(const Index &) = delete;static Index *instance;static std::mutex mutx;public:~Index() {}static Index *GetInstance(){if (instance == nullptr)  //挡住申请锁的繁琐{mutx.lock();// 在多线程下,这里是不安全的,每一线程可以并发的访问单例,创建多个对象,所以这里需要加锁if (instance == nullptr){instance = new Index();}mutx.unlock();return instance;}}// 根据doc_id 找到文档的内容DocInfo *GetForwardIndex(const uint64_t doc_id){if (doc_id >= foward_index.size()){std::cerr << "doc_id out range, error!" << std::endl;return nullptr;}return &foward_index[doc_id];}// 根据关键字获得倒排拉链std::vector<InvertedElem> *GetInvertedElemList(const std::string &word){auto iter = inverted_index.find(word);if (iter == inverted_index.end()){std::cerr << word << "not find in unordered_map" << std::endl;return nullptr;}return &(iter->second);}// 根据去标签格式化的文档, 构建正排 倒排索引bool BuildIndex(const std::string &input){std::ifstream in(input, std::ios::in | std::ios::binary);if (!in.is_open()){std::cerr << input << "open error" << std::endl;return false;}std::string line;while (std::getline(in, line)){DocInfo *doc = BuildForwardIndex(line);if (doc == nullptr){std::cerr << "build error" << std::endl;continue;}BuildInvertedIndex(*doc);}return true;}private:DocInfo *BuildForwardIndex(const std::string &line){// 解析 line, 字符串切分std::vector<std::string> results;// 将htnl文件解析出来之后 使用sep作为分隔符const std::string sep = "\3";ns_util::StringUtil::CutString(line, &results, sep);if (results.size() != 3){return nullptr;}// 字符串填充到DonInfoDocInfo doc;doc.title = results[0];doc.content = results[1];doc.url = results[2];doc.doc_id = foward_index.size(); // 对应的ID就是数组下标// 插入到正排索引的foward_index中foward_index.push_back(doc);return &doc;}bool BuildInvertedIndex(const DocInfo &doc){// 分词std::vector<std::string> title_words;struct word_cnt{int title_cnt;int content_cnt;word_cnt() : title_cnt(0), content_cnt(0) {}};// 用在保存词频的映射表std::unordered_map<std::string, word_cnt> word_map;ns_util::JiebaUtil::CutString(doc.title, &title_words);for (std::string &s : title_words){word_map[s].title_cnt++;}std::vector<std::string> content_words;ns_util::JiebaUtil::CutString(doc.content, &content_words);// 对内容进行词频统计for (auto &s : content_words){word_map[s].content_cnt++;}for (auto &word_pair : word_map){InvertedElem item;item.doc_id = doc.doc_id;item.word = word_pair.first;item.weight = 10 * word_pair.second.title_cnt + 1 * word_pair.second.content_cnt;std::vector<InvertedElem> &inverted_list = inverted_index[word_pair.first];inverted_list.push_back(std::move(item));}return true;}};Index *Index::instance = nullptr;
}

编写搜索引擎模块 Searcher
#pragma once
#include "index.hpp"
#include"util.hpp"
#include<algorithm>
#include<jsoncpp/json/json.h>namespace ns_seacher
{class Searcher{private:ns_index::Index* index;  //获取单例对象索引public:Searcher() {}~Searcher() {}void InitSearcher(const std::string &input){// 获取或者创建index对象index = ns_index::Index::GetInstance();// 根据index对象获取索引index->BuildIndex(input);}// query搜索关键字   返回给用户浏览器的数据  搜索结果void Search(const std::string &query, std::string *json_string){// 将关键字分词  -> 查找std::vector<std::string>words;ns_util::JiebaUtil::CutString(query,& words);// 根据分词搜索std::vector<ns_index::InvertedElem> inverted_list_all;for(std::string word : words){boost::to_lower(word);std::vector<ns_index::InvertedElem> *inverted_list = index->GetInvertedElemList(word);if(inverted_list == nullptr){continue;}inverted_list_all.insert(inverted_list_all.end(), inverted_list->begin(), inverted_list->end());}// 汇总查找结果,按照相关性  降序排序std::sort(inverted_list_all.begin(), inverted_list_all.end(), [](const ns_index::InvertedElem& e1, const ns_index::InvertedElem& e2){return e1.weight > e2.weight;});// 查找出来的结果,构建json串Json::Value root;for(auto& item : inverted_list_all){//查正排ns_index::DocInfo*  doc = index->GetForwardIndex(item.doc_id);if(nullptr == doc){continue;}Json::Value elem;elem["title"] = doc->title;elem["desc"] = GetDesc(doc->content, item.word);elem["url"] = doc->url;root.append(elem);}Json::StyledWriter writer;*json_string = writer.write(root);   //序列化}//截取内容的简化,显示在摘要中std::string  GetDesc(const std::string& content, const std::string& word){//找到关键字在content中的首次出现  ,然后往前找50字节(如果没有50字节,从begin开始),往后找100字节(截取到end)const std::size_t prev_pos = 50;const std::size_t next_pos = 100;//找到content中word首次出现的位置std::size_t pos = content.find(word);//获取Start endstd::size_t start = 0;std::size_t end = content.size() - 1;if(pos - prev_pos >= start)start = pos - prev_pos;if(pos + next_pos <= end)end = pos + next_pos;//截取子串if(start >=  end)return "None";return content.substr(start, end - start);}};
};

编写http_server 模块

cpp-httplib库:https://gitee.com/zhangkt1995/cpp-httplib?_from=gitee_search

注意:cpp-httplib在使用的时候需要使用较新版本的gcc,centos 7下默认gcc 4.8.5
升级gcc

安装scl,升级gcc版本
sudo yum install centos-release-scl scl-utils-build
升级gcc
sudo yum install -y devtoolset-7-gcc devtoolset-7-gcc-c++
启动新版本的gcc:命令行启动,只在本次会话中有效
scl enable devtoolset-7 bash
安装cpp-httplib
最新的cpp-httplib在使用的使用如果不是较新的gcc的话,运行的时候会有问题,建议使用cpp-httplib 0.7.15
https://gitee.com/welldonexing/cpp-httplib/tree/v0.7.15下载zip,将文件拖拽到服务器上,使用unzip解压当前的cpp-httplib.zip文件

注意: 如果我们在测试的时候出现下面这个情况的话,说明gcc/g++版本有点低,使用cpp-httplib的时候会报错
在这里插入图片描述

//解决方法:
scl enable devtoolset-7 bash  // 注意这个方法只在本次会话中有效(gcc/g++)//2,将上述命令添加到~/.bash_profile文件中 每一次启动bash进程的时候都会运行这个文件

测试cpp-httplib
在这里插入图片描述
在这里插入图片描述
httpserver.cc的编写

#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_seacher::Searcher search;search.InitSearcher(input);httplib::Server svr;svr.set_base_dir(root_path.c_str());//我们这里模仿百度 用户的请求参数在url中的样式svr.Get("/s", [&search](const httplib::Request & req, httplib::Response &rsp){//这里是判断url是否存在指定参数名的参数if(!req.has_param("word")){//汉字对应text/plainrsp.set_content("必须有搜索关键字!","text/plain; charset=utf-8");return;}std::string word = req.get_param_value("word");std::cout << "用户正在搜索:" << word << std::endl;std::string json_string;search.Search(word, &json_string);rsp.set_content(json_string, "application/json");});svr.listen("0.0.0.0",8081);return 0; 
}

编写前端代码

1.先搭好框架 HTML

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Boost 搜索引擎</title>
</head>
<body><div class="container"><div class="search"><input type="text" value="输入搜索关键字"><button>皮蛋搜索</button></div><div class="result"><div class="item"><a href="#">这是标题</a><p>这是摘要</p><i>http://search</i></div><div class="item"><a href="#">这是标题</a><p>这是摘要</p><i>http://search</i></div><div class="item"><a href="#">这是标题</a><p>这是摘要</p><i>http://search</i></div><div class="item"><a href="#">这是标题</a><p>这是摘要</p><i>http://search</i></div><div class="item"><a href="#">这是标题</a><p>这是摘要</p><i>http://search</i></div><div class="item"><a href="#">这是标题</a><p>这是摘要</p><i>http://search</i></div> </div></div>
</body>
</html>

2.编写css样式

<style>/* 去掉网页所有的内外边距 */* {/* 设置外边距 */margin: 0;/* 设置内边距 */padding: 0;}html,body {height: 100%;}.container {width: 800px;margin: 0px auto;margin-top: 15px;}.container .search {width: 100%;height: 52px;}/* input在进行高度设置的时候需要考虑边框的问题 */.container .search input {float: left;width: 600px;height: 50px;border: 1px solid black;border-right: none;padding-left: 10px;color:  68, 67, 67;font-size: 15px;}.container .search button {float: left;width: 150px;height: 52px;background-color: #4e6ef2;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;font-size: 22px;color: #4e6ef2;}.container .result .item a:hover {color: #2c428b;text-decoration: underline;/* 深蓝色 */}.container .result .item a:active {color: #335b9b;/* 点击后的颜色 */}.container .result .item p {margin-top: 5px;color: #71777D;font: 16px / normal Arial, Helvetica, Sans-Serif;}.container .result .item i {display: block;font-style: normal;color: #4007A2;}</style>

3. 编写js代码

引入JQuery: <script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>

<script>function Search(){//1.提取数据let query = $(".container .search input").val();console.log("quety=" + query);//2.发起http请求  ajax:属于一个和前后端进行数据交互的函数 Jquery中的$.ajax({type:"GET",url:"/s?word=" + query,success:function(data){console.log(data); BuildHtml(data)}});}function BuildHtml(data){let result_lable = $(".container .result");result_lable.empty();for(let elem of data){let a_lable = $("<a>",{text: elem.title,href: elem.url,//跳转到新的页面target: "_blank"});let p_lable = $("<p>", {text: elem.desc,});let i_lable = $("<i>", {text: elem.url});let div_lable = $("<div>", {class: "item"});a_lable.appendTo(div_lable);p_lable.appendTo(div_lable);i_lable.appendTo(div_lable);div_lable.appendTo(result_lable);}}</script>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/836795.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux本地部署Nightingale夜莺监控并实现远程访问提高运维效率

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

视频剪辑的技巧:掌握如何高效批量调整视频尺寸的方法

在视频剪辑的过程中&#xff0c;调整视频尺寸是一个常见的需求。无论是为了适应不同平台的播放要求&#xff0c;还是为了统一多个视频的尺寸以提升观看体验&#xff0c;掌握高效批量调整视频尺寸的技巧都显得尤为重要。本文将为您详细介绍云炫AI智剪如何高效地进行这一操作&…

通往糊涂之路 The road to serfdom

最近被推送了一本书&#xff0c;哈耶克的............ 试一试&#xff0c;看看能不能看懂&#xff0c;也许是通往糊涂之路。

Windows Qt中支持heic 图片显示

安装vcpkg&#xff1a; git clone https://github.com/microsoft/vcpkg 执行脚本&#xff1a; .\vcpkg\bootstrap-vcpkg.bat 在安装之前如果需要指定vs的编译器&#xff0c; 在如下文件中做更改&#xff0c; 我指定的是用vs2019编译的&#xff1a; D:\vcpkg\vcpkg\triplets 增…

谈 postman自动化接口测试

背景描述 有一个项目要使用postman进行接口测试&#xff0c;接口所需参数有&#xff1a; appid: 应用标识&#xff1b; sign&#xff1a;请求签名&#xff0c;需要使用HMACSHA1加密算法计算&#xff0c;签名串是&#xff1a;{appid}u r l {url}url{stamp}&#xff1b; stam…

AlphaFold3—转录因子预测(实操)

写在前面 我们上一次已经介绍了如何使用AlphaFold3&#xff1a;最新AlphaFold 3&#xff1a;预测所有生物分子结构、相互作用 AlphaFold3可以做什么&#xff1f; 1.AlphaFold服务器可以对以下生物分子类型进行建模&#xff0c;评价其相互结合&#xff1a; 蛋白质 DNA RNA 生…

课题组里有一个卷王是什么体验?

::: block-1 “时问桫椤”是一个致力于为本科生到研究生教育阶段提供帮助的不太正式的公众号。我们旨在在大家感到困惑、痛苦或面临困难时伸出援手。通过总结广大研究生的经验&#xff0c;帮助大家尽早适应研究生生活&#xff0c;尽快了解科研的本质。祝一切顺利&#xff01;—…

SpringBoot项目配置HTTPS接口的安全访问

参考&#xff1a;https://blog.csdn.net/weixin_45355769/article/details/131727935 安装好openssl后&#xff0c; 创建 D:\certificate CA文件夹下包含&#xff1a; index.txt OpenSSL在创建自签证书时会向该文件里写下索引database.txt OpenSSL会模拟数据库将一些敏感信息…

光伏EPC管理软件都有哪些功能和作用?

光伏EPC管理软件是用于光伏工程项目管理的综合性工具&#xff0c;它涵盖了从项目策划、设计、采购、施工到运维的各个环节。 1、项目总览 管理所有项目计划&#xff0c;包括项目类型、项目容量等。 调整和优化项目计划&#xff0c;以应对不可预见的情况。 2、施工管理 制定…

中学生政史地杂志中学生政史地杂志社中学生政史地编辑部2024年第3期目录

每月时政 时政要闻&#xff08;2024年2月&#xff09; 李伟; 3-12 热点聚焦 关注2024年全国两会 汤健云; 13-15 积极应对老龄化&#xff0c;发展银发经济 王吉兴; 16-18《中学生政史地》投稿&#xff1a;cn7kantougao163.com “一带一路”助力柬埔寨经济发展 李…

上海计算机学会2022年5月月赛C++丙组T3打印金字塔

题目描述 给定一个整数 n&#xff0c;请打印一个具有 n 层结构的三角形金字塔&#xff0c;例如当 n3 时&#xff0c;打印如下图形&#xff1a; /\ /__\/\ /\/__\/__\/\ /\ /\ /__\/__\/__\输入格式 单个整数&#xff1a;表示 n。 输出格式 根据题意输出层次为 n 的三角形…

迅睿CMS中实现关键词搜索高亮

在迅睿CMS系统中实现关键词搜索高亮是提升用户体验和搜索效果的重要手段。当用户搜索某个关键词时&#xff0c;将搜索结果中的关键词高亮显示&#xff0c;可以帮助用户更快速地定位到所需信息。 关键词高亮的实现 在迅睿CMS中&#xff0c;你可以使用内置的dr_keyword_highlig…

【其他学习参考文档记录】

交叉编译学习参考 nodejs 交叉编译-cliff工作室

2024年淘宝天猫618超级红包领取口令活动时间是从什么时候开始到几月几号结束?

2024年淘宝天猫618活动&#xff0c;将于2024年5月19日开始&#xff0c;今年618淘宝天猫取消了预售环节。同时&#xff0c;618淘宝天猫也提供了多项优惠活动&#xff1a;超级红包、跨店满减、官方立减、全程价保及草柴APP领优惠券拿购物返利等多重优惠活动。 2024年淘宝天猫618…

自媒体从0-1起号全流程落地指南。(含工具)

下面开始进入主题&#xff1a; 一、持续涨粉的技巧 持续账号的账号通常是具备以下的几种特征 ①利他性&#xff1a;利他性的核心在于你向用户提供了什么&#xff1f; 可以透过逆向思维来体现&#xff0c;首先要明确目标人群及其需求&#xff0c;然后根据这些需求提供必要的…

HarmonyOS开发案例:【UIAbility内和UIAbility间页面的跳转】

UIAbility内和UIAbility间页面的跳转&#xff08;ArkTS&#xff09; 介绍 基于Stage模型下的UIAbility开发&#xff0c;实现UIAbility内和UIAbility间页面的跳转。包含如下功能&#xff1a; UIAbility内页面的跳转。跳转到指定UIAbility的首页。跳转到指定UIAbility的指定页…

C语言学习(十)结构体

目录 一、结构体类型定义二、结构体变量的定义三、结构体变量赋值1. 定义结构体变量的同时进行赋值2. 定义结构体类型的同时定义变量并进行赋值3. 在定义结构体变量时对指定成员进行赋值4. 在定义完结构体变量后&#xff0c;通过.进行赋值 四、结构体成员访问五、结构体内部指针…

MYSQL:MySQL 事务隔离级别详解

一、MySQL事务是什么&#xff1f; MySQL事务是一组在数据库中执行的操作&#xff0c;这些操作要么全部成功执行&#xff0c;要么全部不执行&#xff0c;以确保数据库的完整性和一致性。 事务的 ACID 事务具有四个特征&#xff1a;原子性&#xff08; Atomicity &#xff09;、…

用Robotframework+selenium 进行webui页面自动化测试

Robotframework其实就是一个自动化的框架&#xff0c;想要进行什么样的自动化测试&#xff0c;就需要在这框架上添加相应的库文件&#xff0c;而用于webui页面自动化测试的就是selenium库. ​ 关于robotframework框架的搭建我这里就不说了&#xff0c;今天就给大家根据一个登录…

HarmonyOS开发案例:【Stage模型下Ability的创建和使用】

介绍 基于Stage模型&#xff0c;对Ability的创建和使用进行讲解。首先在课程中我们将带领大家使用DevEco Studio创建一个Stage模型Ability&#xff0c;并使用UIAbilityContext启动另一个Ability&#xff0c;然后借助Want&#xff0c;在Ability之间传递参数&#xff0c;最后我们…