じつは重複する要因が複数あって面倒。
RSSリーダでさえニュースが重複して表示される例
RSSにフィードを登録する。フィードは同一ソース源でありながら、異なるカテゴリのRSSとする。そして、ニュースが重複するものとする。このとき、RSSリーダ内にはニュースが重複して表示される。
別ニュース源なら仕方ないと思うが、同一ソース源でURLも同じなら省いて欲しい。まさか同一ソース源でURLが違う同一ニュースなどないだろうから、これで重複を省けるはず。なのに表示されてしまう。
ニュースが重複する要因
1. 同一ソース源における別カテゴリRSS
あるニュースサイトでは、複数のカテゴリに分けてRSSを発信している。それらのニュースが重複する場合がしばしばある。
やっかいなことに、公開日時が異なる。url
,title
は同じなのに公開日時だけが違う。もちろん同一記事だ。これも削除対象。だが、これは今までのようにSQLite3の表制約UNIQUE(published,url)
では防げない。UNIQUE(url,title)
とすべきだろう。
だがUNIQUE(url,title)
だと、過去の記事と重複する可能性がある。ニュースサイトゆえにURLは一定期間で使い回すことが想定される。タイトルも完全一致することが無いとはいえない。人に見せようとするタイトルの付け方は偏るから。
2. RSSと既存DB
RSSには取得済みの内容がありうる。RSSとDBの整合性はユーザが自力でとらねばならない。なぜならニュースの取得は以下のような仕組みだから。
- RSS更新タイミングはプロバイダ依存である
- それがいつになるかはプロバイダでさえ予想できない
- ニュースとなる事件・事故がいつどこで起きるかわからない
- それがいつになるかはプロバイダでさえ予想できない
- ニュース取得はPull方式である(サーバPush方式ではない)
- ユーザがリクエストしたタイミングで取得される
もしこれがRSSなどのフィードではなくWebAPIなら少しは楽ができただろう。引数に日時を渡して、それ以降の記事だけを取得できるなら。だが、DBと自動的に整合性を保ってくれるわけではない以上、やはり日時チェックが必要になってしまうのは変わらない。
3. 別ソース源ごとに異なる更新日時
ニュースは複数のソース源から得るだろう。その上でDBはひとつに統合したい。このときDB内には別ソース源の最新更新日時が含まれている。
最新ニュースだけを取得したいときは、DB内にある最新日時より新しいニュースだけを取り込めばいい。だが、もしこのDBに異なるソース源の日時が混在していたら、ニュースの取りこぼしが発生しうる。
たとえばソースAで2019-08-01 23:59:59
のニュースを取り込んだが、ソースBで2019-08-01 19:00:00
のニュースを取り込みたい場合がある。このとき、DB内で最新日時を取得し、それより新しいもののみを取得しようとすると、ソースBのニュースが取り込まれなくなってしまう。
select max(published) from news;
これを解決するには、ソースごとに別々のDB内最新日時を取得する必要がある。SQLでいうと以下。
select s.source_name, max(n.published) from news n, source s group by news.source;
ソース源を管理するのは面倒なので、
url
のドメイン名でソース源を特定してもいいかもしれない。
select ドメイン名, max(published) from news group by ドメイン名;
たとえばurl
が以下のような場合。
url|published http://A.co.jp/news1|1999-12-31 http://A.co.jp/news2|2000-01-01 https://B.co.jp/news1|2010-01-01 https://B.co.jp/news2|2010-12-31
ソース源は以下のようになる。
domain|max(published) A.co.jp|2000-01-01 B.co.jp|2010-12-31
url
からどうやってドメイン名を取得するか、という課題が残る。また、同一ソース源が必ず同一ドメインのURLだけを持っているかわからない。ふつうはそのはずだが。
おまけ。URLの部位名称は以下。
http://www.ytyaru.co.jp:80/dir/index.html?param1=111¶m2=222
部位 | 名称 |
---|---|
http |
スキーム |
www |
ホスト名 |
ytyaru.co.jp |
ドメイン名 |
80 |
ポート番号 |
dir/index.html |
パス |
? 以降 |
クエリ。Key=Value の形式。& で複数列挙。 |
ソースごとに最新日時を取得すると、一気に難しくなる。対策を考えてみた。
* RSSのURL/記事URLパターン/ソース源名/DBファイル名の対応表
できればURLから自動的にDBファイル名などを得たい。/
,.
,:
などURLには記号が多い。これらをファイルシステムでも使える名前にすべく_
に置換するなどの対応が必要か。www_ytyaru_co_jp.db
のような。
また、同一ソース源の別カテゴリRSSなら、それらを同一ソース源としてまとめておきたい。RSSのドメイン名が同一なら同一ソース源と判断していいと思う。
所感
じつは整合性を保つだけならinsert or ignore ...
で十分。重複したら挿入しない。だが、これだと応答速度がとてつもなく遅い。20〜24秒/1件。原因は、重複しているときでも常にURL先からHTML取得しているからだと思う。
そこで、挿入データを取得する前に重複チェックして、重複したものは本文抽出など重い処理をしないようにしたかった。すると今回のように面倒なことになってしまう。
まさか自力で整合性を保つことがこれほど面倒とは思わなかった。原因はソースごとの分離。もうソースは固定1つでいいと妥協しそうになる。だが、これはサービス終了される危険性がある以上、すべきでない。
さて、どうするか。
対象環境
- Raspbierry pi 3 Model B+
- Raspbian stretch 9.0 2018-11-13 ※
- bash 4.4.12(1)-release ※
- Python 3.5.3
- SQLite 3.29.0 ※
- MeCab 0.996ユーザ辞書
$ uname -a Linux raspberrypi 4.19.42-v7+ #1218 SMP Tue May 14 00:48:17 BST 2019 armv7l GNU/Linux