一些SEO进行网站开发/数据分析的利器
时间:2023-05-26 01:48:02 | 来源:网站运营
时间:2023-05-26 01:48:02 来源:网站运营
一些SEO进行网站开发/数据分析的利器:我的开发经验是从初中开始的,用文曲星里面的GVBASIC编写一些MOD游戏;高中印象比较深的,是用VB给小说写了一个分割程序——iPod备忘录可以用来看小说,但每个文件只支持几百几千字,很有限,于是要把大文件分割成诸多小文件。
大一时候,成为了学校上万个新生里面的几十个计算机基础免修的之一。我们几十个人的课改为了C语言,在学了点最皮毛的C语言之后差点没被它劝退。
于是其实学生时期也没学到什么管用的开发技能。但至少有一个好处是,当我做SEO之后,立马就意识到了开发网站、分析数据是需要借助技术手段的,立马去学了PHP,后来发现Python更好用,于是从2011年开始写Python至今。
在之前有几年的时间里面,我会着力推崇技术优先。乃至于在面试他人的时候,我会考验别人写正则表达式的能力。
至今,我仍然非常明确的认为,尽管懂正则表达式远远不意味着能做好SEO,但反过来,完全不会正则表达式的人,也是不可能拥有优秀的SEO能力的。因为SEO的核心重点之一是研究排名,而研究排名是需要分析数据的,分析数据又是离不开正则表达式的。
xxx.com/zufang/123.htmlxxx.com/zhaopin/456.htmlxxx.com/zufang/beijing/xxx.com/zufang/list.html如何便捷且精确的把前两个URL归为一类去进行分析,就是个典型的正则表达式问题。
但我现在通常不会多加强调技术这回事了。技术能力对于SEO是必备的,但也只是必备的而已。
这些年我逐渐意识到,SEO是一种最典型的研究性领域,和物理学等领域的研究在本质上可能并无差别。说到研究,那就是统计数据(或是收集非数据性的事实)->获得猜想->验证猜想。其中,一些数据或非数据的分析思路方向(乃至说是哲学)的重要性,无疑是远高于获取数据来分析这种技能本身的。
(但有个特殊的方面是,如果懂得些技术原理,可以更容易理解搜索引擎的运作机制。尤其若懂得机器学习、深度学习的底层原理,诸如深刻理解它们的结果,是基于训练集的通过归纳法所得出的结论,会显著利于理解一些由这些手段弄出来的,偶尔与常识相违背的排序因素)
但仍然,掌握些更适用的技术手段,有时可以给我们节省下极大量的时间。尤其当我们想自己做网站来获取SEO流量的时候,那么自行从框架级别开发,无论是网站性能负载或是功能的可扩展性上面,都会远远优于使用一些现成CMS。因此,本文介绍些我所采用的高效技术手段。
我认为把做SEO的30%时间放到写代码上去,会是一个比较合适的投入比例。应当明白的是,许多技术圈子更主流的解决方案,在代码实现上非常之臃肿,这主要是基于大型项目的开发层面上,可维护性是非常重要的考量。如果自己写出的代码,换一个人很难接手去修改,乃至于自己隔了几个月回来再看也觉得很陌生从而无从下手,那么,哪怕这套技术解决方案可以用十行代码搞定别人几百行代码搞定的事情,它通常仍然不会成为最主流的技术解决方案。
但对于非专业技术人员而言是相反的,技术开发的简单程度及开发效率往往非常关键。可维护性就相对次要许多了——如果不给网站乱加功能,那么大不了重构一整个网站也很可能不过是几天的事。
本文介绍的都是简单高效的技术手段。
1. Python下载网页 PycURL->Requests
我将PycURL替换为Requests是几年前的事情了。因为Requests现在已经是Python内置库,想来应该大部分人都已经用它了。但早在Requests还没有流行之前,我自行包装了PycURL来使用。尽管对于单独下载一个网页内容这样的简单需求,使用起来是简单了,但底层代码实在谈不上简洁优雅:
import pycurl, StringIOdef curl(url): s = StringIO.StringIO() c = pycurl.Curl() c.setopt(pycurl.URL, url) c.setopt(pycurl.REFERER, url) c.setopt(pycurl.FOLLOWLOCATION, True) c.setopt(pycurl.TIMEOUT, 60) c.setopt(pycurl.ENCODING, 'gzip') c.setopt(pycurl.USERAGENT, 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36') c.setopt(pycurl.NOSIGNAL, True) c.setopt(pycurl.WRITEFUNCTION, s.write) for k, v in kwargs.iteritems(): c.setopt(vars(pycurl)[k], v) c.perform() c.close() return s.getvalue()html = curl(url)
而Requests库,口号是「HTTP for Humans」,下载一个网页只需要一行:
import requestshtml = requests.get(url).text
这样的语法,无疑是更适合非专业开发人员的。大多数情况下,我们并不想涉及到底层实现里面去,而是单纯想要只用一句代码就解决一个问题。Requests正是较为完美的实现了这一点。
下面将推荐的一些,都有着类似于PycURL迁移到Requests这样的巨大的便捷性提升。
2. 全文检索 ElasticSearch->MeiliSearch
全文检索在SEO的主要运用场景,是做聚合页和做相关文章匹配。
ElasticSearch之所以快速占据了全文检索的大半江山,一方面肯定是性能,另一方面想来也有代码实现简洁的功劳。毕竟它对比起上一代的Sphinx等全文检索,已经省事了太多了。
尤其在使用了ElasticSearch-DSL以后,一些基础索引+查询需求的代码量可以从几十行缩短到十行左右,一度被我认为已经接近是最理想选择了。
直到发现了MeiliSearch,对比之下顿时认为ElasticSearch是多么臃肿到可怕的程度。或许不能算是ElasticSearch的问题,只是MeiliSearch在代码简洁性上面实在太极致了。
Meili看上去像是个国产粗制劣造全文索引的随意名字,但实际上它是个国外开发的,基础功能健全且非常高效的全文检索。除非有些高级的功能需求,或是数据体量极大乃至需要集群,否则至少我目前看来它基本上足够替代ElasticSearch。
import meilisearchidx = meilisearch.Client('http://127.0.0.1:7700').index('test')# 索引idx.add_documents(docs) # docs是包含一个或多个dict的list# 查询for hit in idx.search(query)['hits']: print(hit)
在基础搜索需求下,没有任何一行多余的代码。
如果对全文检索的原理有所了解,那么应该知道,因为倒排索引要反过来匹配正排索引,所以每个文档必须有一个唯一ID。
对于MeiliSearch,当首个插入文档里面有一个key包含「id」的时候,无论它是「id」、「uid」还是「_id」等,都会自动被设置为唯一ID(当然也可以自行设置)。因为大部分情况下,数据库里面读出来的数据都是会有一个唯一字段是命名包含ID的,所以这样的自动ID规则使得大部分情况下,数据库读出来的数据可以不经任何修改与配置,直接存到全文索引里面。
除了代码层面以外,环境配置上,MeiliSearch也比ElasticSearch省事得多。单文件即可运行,自身支持中文分词(jieba实现)无需安装插件之类的,部署起来两分钟的事。
3. 浏览器自动化 Selenium->Playwright
浏览器自动化在SEO的主要应用场景,是在一些防采集比较严的网站,或是在一些JavaScript使用较多的网站,去做数据抓取。比如Google的SERP或是百度凤巢的搜索词数据。
当然它也是快排基础。只不过懂得用浏览器自动化手段,和做出快排效果那还差了十万八千里。
Selenium大概是我之前几年之中日常会用的里面,语法最让人难受的了,以点击一个页面元素为例:
driver.find_element_by_css_selector('div#content').click()
来看一下微软最近刚开源的Playwright:
page.click('div#content')
默认直接采用最常用的CSS选择器。那比如要用xpath选择器该怎么办呢?
page.click('//html/body')
若是//开头就转为使用xpath选择器,多么简洁。
不仅是语法,其余许多方面它也对比起Selenium有巨大的优势。而且还有个很好用的操作录制功能,可以省下大部分的开发时间。
4. 前端CSS框架 Bootstrap->Tailwind
在Bootstrap4版本出了以后,我已经感觉它很好用了,一些实用类足以解决大部分样式问题,乃至于我越来越少的用到它自己提供的组件样式。
Bootstrap5提供了更多的实用类,开始显得更好用了。但它考虑到有少数国家语言的阅读习惯是从右到左,因此pl(padding left)之类的都改成了ps(padding start),让我这样不需要去做多语言网站的人,觉得特别的别扭。
这就愈发提供了理由去转为一个更专注于实用类的CSS框架,诸如Tailwind。
<div class="rounded border shadow p-4 hover:bg-gray-600"> content</div>
Bootstrap里面有提供少量预置的背景渐变,而Tailwind里面的背景渐变能指定渐变方向,以及起始、终止颜色;
Tailwind的实用类对于字体大小,内间距、外间距都大量梯度可选择。除非对于大小、间距有着特别的苛求,否则都可以只通过添加class搞定;
Tailwind还支持把hover等选择器也都用class搞定,这也是让我觉得非常惊艳的一个地方。
我用它尝试重构了我的一个网站的CSS。原本在使用了Bootstrap实用类的情况下还写了一百多行CSS,用了它就只剩下几行了(因为有个需要使用nth-child选择器的地方它不支持)。
5. 网站架构
其它的一些主要技术解决手段,我在这几年里面没有更换过,简述一下:
网站架构:Linux + Nginx + Python(Flask) + NoSQL(MongoDB)
绝大部分CMS背后,都是PHP+MySQL的解决方案,乃至其中大部分CMS还是生成静态页面(默认不生成静态的WordPress只要几万文章就撑不太住了),这是非常不科学的。
若十年使用PHP+MySQL的解决方案,那是因为它比起ASP等还是具有一定优势的,相对容易开发,此外没什么其它更多选择。但尤其2014年之后Python和NoSQL都火了起来,在一些简单场景下,它们早就是性能与开发效率层面上都好上十倍百倍的选择了。
(我是2011年就从PHP转为写Python的,当时Python和Ruby孰优孰劣都还没有绝对定论,但哪怕在当时,明眼人看起来结论应该都是毋庸置疑的)
当然时至如今,MySQL之类的关系型数据库,在大部分业务场景下是比NoSQL更好的选择,但我们讨论的是CMS等基础建站场景。CMS是内容管理系统,核心该做的事情无非就是把数据库里面有的内容展现给用户。这正是NoSQL及其设计准则最擅长的场景。
至于数据与数据之间有什么内在关系,不是CMS应该去多加关心的,也因此没有用关系型数据库的必要性。
以上说的这些,对于没有从Python框架用NoSQL写过网站的人,可能比较抽象,不太容易具体的理解。所以我说些相对具体的可参考数字。
我之前开发过一个网站。有几千万的页面、几百万的日均PV(其中有近30万UV是用户流量,其余是爬虫抓取量),每个页面都有全文检索匹配/随机匹配等规则的区块展示。在这访问规模下:
绝大部分国产CMS若不做静态化页面都是撑不住的。尽管如果用月均几千元配置的服务器,做了静态页面多半还撑得住,但每次更新几千万的页面会是个耗时极久的事情。为了在更新页面的时期网站访问不会崩溃,可能还要搞至少两台服务器做MySQL的读写分离。通常Memcached、Redis之类的缓存都得用上。
而我实际用的就是一台4核8G的阿里云ECS,每月几百元(但额外带宽费用相对还要贵不少,没办法),且绝大部分时间CPU及内存的冗余都极多,正常运行期间CPU占用到不了10%,于是极限情况下1核2G也应该是够的。
一开始我还做了在代码层面做了页面级的LRU缓存和区块数据的TTL缓存,这本身是对应场景下理论几乎最优的缓存策略,但后来才发现其实基本没必要。只要数据逻辑没往复杂了写,服务器性能怎么都够用。
毕竟偌大一个网站的后端总共就百来行代码,能慢到哪儿去呢。反观CMS,一个页面的生成背后,动辄几千几万行代码。最终产出的一个页面上显示出来的就那么多东西,用手指往往都数得过来,每个小小东西后面平均对应几百行以上的代码,想想那种代码可能合理吗?
我们应当去尽量遵循一个对于业余开发人员比较适用的开发准则:做一件事尽可能只用一行代码。(比如做一个相关文章区块的内容匹配,需要一行代码还是几十行代码?)
如果几行代码里面还搞不定,那么不应当去设法写更多代码,而该是转为去找寻更简洁易用的解决方案。
下面的代码是我的一个电影站,其背后的核心代码部分:
from flask import Flask, render_template, request, abortimport pymongo, meilisearchdb = pymongo.MongoClient().videoidx = meilisearch.Client('http://127.0.0.1:7700').index('video')idx.add_documents([ doc for doc in db.video.find() ])app = Flask(__name__)@app.route('/video/<int:_id>/')def video(_id): doc = db.video.find_one({'_id': _id}) if doc.get('delete'): abort(404) return render_template('video.html', random_video = db.video.aggregate([{'$match': {'category': doc['category']}}, {'$sample': {'size': 12}}]), **doc )@app.route('/video/<int:_id>/<source>-<int:play_id>.html')def play(_id, source, play_id): doc = db.video.find_one({'_id': _id}) if doc.get('delete'): abort(404) play_list = doc['source']['online'][source][play_id-1] return render_template('play.html', play_url = play_list['url'], play_label = play_list['label'], **doc )@app.route('/search/')def search(): query = request.args.get('query') return render_template('search.html', results = idx.search(query)['hits'], query = query )@app.route('/example-admin-path/')def admin(): info = '' if request.args.get('action')=='delete': if request.args.get('password')=='ExamplePassword': _id = int(request.args.get('_id')) db.video.update_one({'_id': _id}, {'$set': {'delete': True}}) info = '<a href="/video/%d/">页面已删除</a>' else: info = '操作密码错误' return render_template('admin.html', info=info)
在这几十行的后端代码里面,除了视频简介与播放页面,还实现了搜索功能及支持屏蔽具体某个视频的管理后台。靠这几十行代码做出来的页面,和大部分电影网站CMS默认模板下的样子已经差不多了。
我贴出来的只是一部分,还有些非核心功能的被我删减了。但实际线上的代码,在加上首页、栏目页及其他诸多小功能之后,也不过只有一百来行的代码。
尤其在搜索功能和管理后台这块的实现逻辑,读者可以参考一下。因为通常情况下,大部分人做出来的类似功能,动辄几十行以上的代码。
像是一般来说后台管理都是通过用户名和密码这样的cookie来验证用户身份的,这样的常规做法有许多好处,我们未来如果要进一步扩展功能,像是记录某个视频是谁删的(对于一个公司里面可能有很多个员工有操作权限),那么这些常规做法自然是在功能上面更具有扩展性的;
但如果这个网站肯定只是我们自己维护,那么常规后台实现方式就是极度臃肿的。我们只需要一个密码框,来确保哪怕他人发现了这个页面也无法恶意应用,那往往就足够了。
(如果对安全性有更高要求,那么就把GET改成POST,再结合上HTTPS,安全方面基本无忧)
可见,极简的程序逻辑背后,首先是理清楚根本需求所在,剩下的事情往往就很简单了。
这样,整个网站的代码、性能、可拓展性等方面就会有十倍百倍以上的提升。而基于网站可拓展性的提升,网站的SEO流量也就开始拥有了十倍百倍以上提升的机会。
6. 最后
技术毕竟不是我的专业领域,如果文中若有什么写错的毫不奇怪,欢迎指正。
另外,在我常用的技术解决方案里面,现在只有MongoDB相比起来是显得额外臃肿的(但还是远好于MySQL)。
在json储存文档的前提下,除了增删改查只需实现通过索引的WHERE及SORT BY两种查询功能,这种情况下是否有什么解决方案,在Python环境拥有比MongoDB更简单的语法及部署方式,但愿有人能帮忙告知。