Google 自訂搜尋:設定簡單,但調性不符
我使用 Joomla! 系統建置的網站:華燈初上跟部落格都使用 Google 提供的自定義搜尋服務,將 Google 提供的嵌入碼加進佈景主題中,再透過「自訂 HTML」模組將搜尋表單顯示於佈景主題定義的模組位置,站內搜尋服務就完成了。

讓 Google 處理站內搜尋的好處,是將「搜尋網站內容」這件消耗伺服器效能的事委外處理,對於性能不高的共享型虛擬主機空間來說幫助很大,而且「搜尋及索引」就是 Google 的核心業務。
在學習 PHP 語法,製作 ffxitoolbox 第一版時也是使用 Google 自定義搜尋作為站內搜尋功能,不過沒過多久我就發現該服務不適合用在 ffxitoolbox 上,主要有兩點:
- Google 自定義搜尋的搜尋結果是 Google 索引過的內容,所以沒索引到的部分就找不到。加上搜尋功能的使用是輸入素材關鍵字尋找相關配方,搜尋結果是整頁的搜尋結果,點選後還要再找,不怎麼方便。
- 搜尋結果的呈現風格跟網站的設計差別很大,每次使用時很難跟網站畫面聯想在一起,雖然這不是 Google 的錯...
自建搜尋:畫面自行決定,佔用伺服器效能
2021 年初完成 ffxitoolbox 第一版製作,代表我從零開始學習 PHP 有了初步成就:我有能力使用 PHP 跟 MySQL 將 ffxi 的合成配方資料整理成網站內容提供閱覽。
在這之後我開始學習 Laravel 框架,設下的目標就是將 ffxitoolbox 以 Laravel 框架重寫:如果能夠完成代表我已經能用 Laravel 做出網站專案。就在作業接近收尾的階段,「站內搜尋」功能的建置浮出檯面,這次實在不想用 Google 自定義搜尋了...
用 Google 搜尋前人的智慧結晶時,我發現了〈How to add simple search to your Laravel blog/website?〉這篇文章,透過閱讀文章內容逐步操作,我讓 ffxitoolbox 的站內搜尋結果能以想要的方式呈現。

建立簡易站內搜尋的流程是:建立搜尋的 Controller,以 LIKE 語法搭配 %{search}% 搜尋資料庫(合成配方、分解及食物效果)中是否有符合的項目。之後將結果送到 Blade template 輸出,並且設置搜尋路由就可以了。以下是 ffxitoolbox 負責搜尋的 Controller 程式碼:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Discompose;
use App\Models\Foodresult;
use App\Models\Recipe;
class SearchController extends Controller
{
//搜尋功能
public function search(Request $request){
// 取得搜尋關鍵字
$search = $request->input('search');
// 查詢範圍
$recipes = Recipe::query()
->where('name', 'LIKE', "%{$search}%")
->orWhere('material1', 'LIKE', "%{$search}%")
->orWhere('material2', 'LIKE', "%{$search}%")
->orWhere('material3', 'LIKE', "%{$search}%")
->orWhere('material4', 'LIKE', "%{$search}%")
->orWhere('material5', 'LIKE', "%{$search}%")
->orWhere('material6', 'LIKE', "%{$search}%")
->orWhere('material7', 'LIKE', "%{$search}%")
->orWhere('material8', 'LIKE', "%{$search}%")
->get();
$discomposes = Discompose::query()
->where('material1', 'LIKE', "%{$search}%")
->orwhere('name', 'LIKE', "%{$search}%")
->orWhere('HQ1', 'LIKE', "%{$search}%")
->orWhere('HQ2', 'LIKE', "%{$search}%")
->orWhere('HQ3', 'LIKE', "%{$search}%")
->get();
$foodresults = Foodresult::query()
->where('Name', 'LIKE', "%{$search}%")
->get();
$binding = [
'search' => $search,
'discomposes' => $discomposes,
'foodresults' => $foodresults,
'recipes' => $recipes,
];
// 回傳搜尋結果,以搜尋頁面呈現
return view('frontend.search', $binding);
}
}
搜尋結果頁面是自己設計的,所以在搜尋結果中加入編輯功能也不是什麼難事。當配方資料獲得進一步確認時時我可以在前貒登入網站,利用搜尋功能找到編輯項目,選開啟編輯表單更新資料。
以一個全合成種類都有涉獵,常常會需要搜尋配方資料的人來說,當前提供的搜尋功能已經符合我的需求。雖然每次搜尋都會消耗伺服器效能,不過以使用者大概只有自己的情況下不擔心伺服器會被操掛(笑)。
外部資源加自訂顯示:Laravel Scout + Algolia
Google 提供的資源省掉運作負擔,但是頁面呈現不愛;自建站內搜尋結果符合預期,擔心使用的人多造成伺服器負載...有沒有集合兩者優點的第三種方案?有的,就是 Laravel Scout 與 Algolia 服務。
Algolia 以 SaaS 方式提供搜尋服務的,開發者可以將網站資料上傳至 Algolia 製作索引,然後透過自訂的視覺版型呈現搜尋結果,兼具效能與美觀。接下來將透過 Laravel 的 Scout 套件連接 Algolia,製作站內搜尋。
申請 Algolia 服務
前往 https://www.algolia.com/ 註冊帳號,接著為網站建立應用程式(Application)及索引(Index)名稱,之後點選畫面左下方「設定(Settings,齒輪圖示)」畫面中點選「API Keys」取得 Application ID 及 Admin API Key。

下載 Laravel Scout,設定 Algolia 通訊
在終端機畫面下載 Laravel Scout 套件
composer require laravel/scout
發佈套件,會建立 /config/scout.php 設定檔
php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"
在 .env 輸入與 Algolia 溝通所需的資料:Application ID、Admin API Key 及索引名稱。
SCOUT_PREFIX=(索引名稱)
SCOUT_QUEUE=true #是否將索引作業排入隊列,建議為 true
ALGOLIA_APP_ID=(Application ID)
ALGOLIA_SECRET=(Admin API Key)
下載 Algolia 搜尋驅動
composer require algolia/algoliasearch-client-php
Model 加入索引
在要索引的 Model(本例是 Post)引入命名空間
use Laravel\Scout\Searchable;
以及在類別中引入 Searchable trait:
use Searchable;
新增 searchableAs 方法,指定存入索引名
public function searchableAs()
{
return config('scout.prefix'); //回傳 .env 中 SCOUT_PREFIX 參數值
// return 'blog'; //直接輸入回傳的索引名
}
新增 toSearchableArray 方法,指定哪些欄位不要納入索引:
public function toSearchableArray()
{
$array = $this->toArray();
unset($array['category_id']);
unset($array['cover_image']);
unset($array['user_id']);
unset($array['sort']);
unset($array['status']);
unset($array['featured']);
return $array;
}
將資料匯入 Algolia:
php artisan scout:import "App\Models\Post"
回到 Algolia 畫面,會看到索引下已經有記錄了。

負責搜尋的 Controller 及方法
建立負責網站運作的 Controller:SiteController,在其中建立 search 方法負責處理搜尋資料:
php artisan make:controller SiteController
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
class SiteController extends Controller
{
public function search(Request $request)
{
$keyword = $request->search;
$post_results = Post::search($keyword);
if($post_results == null)
{
$post_results = '';
}else{
$post_results = $post_results->orderBy('created_at', 'desc')->paginate(20);
}
return view('search', compact('keyword', 'post_results'));
}
}
增加路由
在 /routes/web.php 增加搜尋路由:
// 搜尋
Route::get('/search', 'App\Http\Controllers\SiteController@search')->name('search');
與搜尋有關的 Blade template
與搜尋有關的 Blade template 有兩個:提供搜尋輸入欄位的 /resources/views/widgets/search.blade.php:
<h3 class="sidebar-title">站內搜尋</h3>
<div class="sidebar-item search-form">
<form action="{{ route('search') }}" method="GET">
<input type="text" name="search">
<button type="submit"><i class="bi bi-search"></i></button>
</form>
</div>
負責顯示搜尋結果的 /resources/views/search.blade.php:
@extends('layouts.master')
@section('meta_description', '搜尋 '. $keyword . ' 的結果')
@section('title', '搜尋 '. $keyword . ' 的結果')
@section('cover_image', Voyager::image('articles/search.png'))
@section('url', url()->full())
@section('nav_home', 'active')
@section('content')
<!-- 網頁路徑 -->
<section class="breadcrumbs">
<div class="container">
<div class="d-flex justify-content-between align-items-center">
<h2>搜尋</h2>
<ol>
{{ Breadcrumbs::render('search') }} {!! $keyword !!} 的結果
</ol>
</div>
</div>
</section>
<!-- 網頁路徑 -->
<section id="blog" class="blog">
<div class="container">
<div class="row">
<div class="col-lg-12 entries">
<article class="entry entry-single">
<div class="entry-content">
@if($post_results !== '')
<h3>包含:<font color="blue">{{ $keyword }}</font> 的文章</h3>
@foreach($post_results as $post)
<h3><a href="/blog/{{ $post->slug }}">{{ $post->title }}</a></h3>
{{ $post->introtext }}
@endforeach
<div class="blog-pagination">
<ul class="justify-content-center">{{ $post_results->links() }}</ul>
</div>
@else
<h3>找不到與 {{ $keyword }} 有關的文章。</h3>
@endif
</div>
</article><!-- End blog entry -->
</div><!-- End blog entries list -->
</div><!-- End blog sidebar -->
</div>
</section><!-- End Blog Single Section -->
@stop
在前端搜尋欄位輸入關鍵字

在搜尋結果頁面觀看相關的文章

結語
站內搜尋是我最後一個實做的主要功能,除了尋求適合的方案之外,想等到網站內容累積到一定程度之後再實做,可以看到多筆資料結果也是原因之一。如果手邊實在沒有資料進行查詢,可以透過 Factory 跟 Faker 的協同運作產生假資料,這樣內容要有幾筆就有幾筆。
Algolia 另有推出 Scout Extended 套件擴展 Laravel Scout 功能,透過聚合器(aggregator)將多個 Model 內容集中在同一索引中,還有在搜尋列輸入時就能即時顯示的 live search 功能要等到實力有所精進後再實做出來。
規劃的專案功能皆以到位,該是把網站放上運作空間,讓 larablog 上線的時候了。
後記
在文章發表之後我繼續尋找有關 live search 的資料想弄清楚運作方式,進而瞭解到:live search 透過 AJAX 方式監聽搜尋欄位的輸入內容,返回找到的結果。
如果是 AJAX 的話那麼可以透過 livewire 做到嗎?參考網路上找到的資訊後有做出類似的成果,以下是過程記錄。
參考資料
- https://medium.com/@branick/search-with-laravel-livewire-cb6dcd4ad541
- https://www.twilio.com/blog/build-live-search-box-laravel-livewire-mysql
安裝 livewire,導入資源
安裝 livewire:
composer require livewire/livewire
在 Blade template 主版檔案嵌入 livewire 資源:在 CSS 引入段落加入:
@livewireStyles
JavaScript 引入段落加入:
@livewireScripts
建立 livewire 元件
php artisan make:livewire search
會產生兩個檔案:
- 類別檔案:/app/Http/Livewire/Search.php
- 視圖檔案:/resources/views/livewire/search.blade.php
編輯類別及視圖
在類別檔案加入搜尋事件程式碼:
<?php
namespace App\Http\Livewire;
use Livewire\Component;
use App\Models\Post;
class Search extends Component
{
public $keyword = "";
public function render()
{
$posts = Post::search($this->keyword)->get();
return view('livewire.search', compact('posts'));
}
}
$keyword 作為接收輸入的變數,初始值是空字串。$posts 儲存搜尋結果(Algolia 索引化後的資料),將結果傳到 search 這個 Blade template。
編輯視圖檔案:定義搜尋欄位外觀,及搜尋結果呈現。
<div>
<input type="text" wire:model="keyword" placeholder="請輸入關鍵字"/>
@if($keyword == "")
@else
<div style="
display:block;
position:absolute;
z-index:+1;
width: 280px;
margin-top:10px;
padding: 10px 10px 0px 5px;
background-color: white;
border: 1px dotted;
border-radius: 10px;
">
@foreach($posts as $post)
<ul>
<li><a href="/blog/{{ $post->slug }}">{{ $post->title}}</a></li>
</ul>
@endforeach
</div>
@endif
</div>
這裡的重點在 wire:model="keyword",livewire 會監聽 $keyword 的變化,即時呈現搜尋結果。
發佈 livewire:
php artisan livewire:publish --config
在前端要呈現 live search 地方加上以下內容,顯示 live search 欄位:
@livewire('search')

尚須改進之處
在搜尋欄位輸入關鍵字後會在下方動態產生相關的文章標題,在呈現有符合 live search 的樣子,只是訊息變動的反應有點鈍。其次因為索引內容的範圍關係,顯示項目部分僅做到標題顯示,理想的情況是呈現的內容能再以出處的分類(如:文章/分類/標籤)分別顯示,這部分等到瞭解 Scout Extend 套件的運作細節後再做改良。
居住在臺灣的 Joomler,期望以程式設計、開放原碼推廣活動收入養活一家老小。
35 歲後改姓李,id 作為曾為郭姓的證明。
FFXI:Abokuo@Sylph鯖、よろしくです。

не минует ни одного вечера, чтобы вы не посмотрели пару фильмов?
Among the hotel's many amenities, guests and visitors will find an extensive gaming area, connect with
popular entertainment, diverse cafes, a beauty, a bplay and a number of various shopping centers.
VPS сервер, VPS сервер — отличный выбор
для разработчиков, стремящихся к достойной
производительности.
for the {most {many years|long time} in the caliente casino login, {all|maximum}
that I can {afford|provide} to lose in one game ({evening|night} or weekend) is $400.
besides a customer get the chance to book a more spacious transport, for example, a comfortable bus capable of transporting up https://www.whatsyourhours.com/latvija/health-beauty/melinda-lindas-skaistuma-studija.
Your deposits and collections are instantaneous.
gamble at Winz bluechip and resort to our exclusive bonus, which not requires wagering.
Casino Security Index compiled on database similar information,
gives an assessment reflecting the safety and truthfulness of the online
casino plus login. Casino Plus is owned by Stotsenberg Leisure Park Corporation
Составляя рейтинг гемов для вас,
наши сотрудники трудятся еще
с 2006-го года, https://ramblermails.
https://registraciavsaita.listbb.ru/viewtopic.php?f=3&t=5156
https://www.aicrowd.com/participants/lagodigardacom
https://www.kristiesjewels.com/index.php?route=journal2/blog/post&journal_blog_category_id=2&journal_blog_post_id=7
Fishing' Frenzy megaways has the fisherman free casino game game in which
users have the opportunity get pleasure from interesting occupation fishing
, and increase own winnings.
this algorithm is embedded in the dapp context using a content-script.
even if is about to happen, rabby this is not a a
real problem from the user's side.
Ищете, где купить VPS дешево,
virtual private server? Вам стоит обратить внимание на рекомендуемые предложения на рынке.
https://rich513.com/
Надежная электроника с быстрой доставкой по всему Казахстану и без космических цен. Многие уже давно заказывают в Salemshop.kz: огромный выбор реальные скидки каждый день гарантия на все и отправка в тот же день - https://salemshop.kz/
users it is offered to participate figure in worldwide network tournaments to compete
for the main prize: 1,500 1inch 1inch tokens. in its current state, 1inch
supports more than 389 sources of liquidity from 11 blockchain and 6.