【開発】.net C#でRSS/Atom feedとか読んでみる
そういやRSSに関してはただの利用者にすぎず、RSSの仕様をちゃんと理解してなかった。
そういう事で、c#でいじってみた。RSSに関しては仕様書読まずな完全な素人ですので。
追記9/18、21、22
幸い、.netには、標準でfeedを扱う事ができる・・のだが、まだ使われてるところの多いRSS1.0には非対応なんですね。もち、仕様的にも現行のRSS2.0とは別物。
RSS1.0は、XML的に問題がある記述がされていて、多分、これのお陰で.netからは「なんすか?RSS1.0ってw」という扱いを受けているに、ちがいない。
ま、この辺は他のサイトの方が詳しいので割愛。
それぞれの判断は、XMLルート要素が、RSS1.0は"rdf:RDF"、RSS2.0は"rss"、Atomは、"feed"で可能なようです。
追記9/21。いや、上の判断は間違いでした。正確に間違いではないけど、RSS0.9やRSS0.91の存在を無視していた。RSS0.9は"rdf:RDF"、RSS0.91は"rss"・・と。んな馬鹿な。どないやねん。attributeで確認してくしかないのかねぇ・・。
ちなみに、ITmediaでは、今でもRSS0.91を配信してるんですよ(RSS2.0と1.0も配信してる)
AtomとRSS1.0はシンプルなので、各要素は簡単に取得できるんですが、鬼門がRSS2.0の方。ざっとRSS配信してるデータを眺めてるだけでも、なんか微妙に違う。
public static RssInfo ReadRSS2(Uri uri) { var feed = LoadFeed(uri); string strGenerator = feed.Generator; return new RssInfo { RssUrl = uri, Title = feed.Title.Text, RssVer = "RSS2.0", Items = (from item in feed.Items let link = item.Links.FirstOrDefault() ?? new SyndicationLink(new Uri("about:blank")) let cat = (from ca in item.Categories select ca.Name).ToArray() let aut = (from au in item.Authors select au.Name).ToArray() let cont = (from itemext in item.ElementExtensions where itemext.OuterName == "encoded" && itemext.OuterNamespace == "http://purl.org/rss/1.0/modules/content/" select (itemext.GetObject())).ToArray() let cre = (from itemext in item.ElementExtensions where itemext.OuterName == "creator" && itemext.OuterNamespace == "http://purl.org/dc/elements/1.1/" select (itemext.GetObject())).ToArray() select new RssItem { Title = string.IsNullOrEmpty(item.Title.Text) ? "" : item.Title.Text, Date = item.PublishDate.DateTime, Link = link.Uri, Description = string.IsNullOrEmpty(item.Summary.Text) ? "" : item.Summary.Text, Category = cat.Length < 1 ? "" : string.Join(",",cat), // Auther = string.Join(",",aut), Auther = cre.Length < 1 ? "" : string.Join(",",cre), FeedID = item.Id, Content = cont.Length < 1 ? "" : string.Join(",",cont), Publisher = string.IsNullOrEmpty(strGenerator) ? "" : strGenerator }).ToArray() }; }
RSS2.0汚いソースですが、これでなんとか読めてます。でも、なんか怪しい。
んで、Atomはこんなにシンプル。
public static RssInfo ReadAtom1(Uri uri) { using (var reader = XmlReader.Create(uri.AbsoluteUri)) { var feed = SyndicationFeed.Load(reader); } string strGenerator = feed.Generator; return new RssInfo { RssUrl = uri, Title = feed.Title.Text, RssVer = "Atom1.0", Items = (from item in feed.Items let link = item.Links.FirstOrDefault() ?? new SyndicationLink(new Uri("about:blank")) let cat = (from ca in item.Categories select ca.Name).ToArray() let aut = (from au in item.Authors select au.Name).ToArray() where item.Content.Type == "html" select new RssItem { Title = string.IsNullOrEmpty(item.Title.Text) ? "" : item.Title.Text, Date = item.PublishDate.DateTime, Link = link.Uri, Description = item.Summary == null ? "" : item.Summary.Text, Category = cat.Length < 1 ? "" : string.Join(",",cat), Auther = string.Join(",",aut), FeedID = item.Id, Content = ((TextSyndicationContent)item.Content).Text, Publisher = string.IsNullOrEmpty(strGenerator) ? "" : strGenerator }).ToArray() }; }
ただ、Atomの場合は、いろんなもん(画像やら)を配信できるので、whereでhtmlだけのものと条件を与えてます。
最初GigazineのAtomデータで試してて、なぜだかエラーになる。と思ったら、概要(Description)が配信されてなかったというオチでした。item.Summary==nullのところで、非常に時間が掛かってしまったという話です。
追記:
色々とRSSを手当たり次第読み込ませてみると、結構読めない所が結構あります・・。
まとめサイトみたいなものないんかなぁ・・。これRSSリーダーとか作ろうとすると結構大変ですね。
見つけたところはこんなもんがありました。
時間表記が若干変、大体のところは<dc:date>2011-09-18T12:23+09:00</dc:date>こんな感じで記載されてるんですが、この”T”に続くのが時間で、+9:00というのは日本時間の事。12:23と時と分を指してるんですが、秒とマイクロ秒??まで指定してるところまであった。
もちろん、マイクロ秒?のところは、全部00になってましたけど。
RSS2.0なんだけどパスが変。これもSyndicateでLoadエラーになってるところ。自前で、起こさないといけないんかなぁ・・。
FeedIDがIDになってない&無い。てっきり、一意なIDだと思ってたんだけど、そうでもないようです。中身も全然違ってたし。そういうのは例外でいいのか?あと、存在しないとか。ま、これはありえるか。
そもそもRSSを返さない。これはすぐ見当がつきましたけど。UserAgentを見て判断してるところです。phpなんかで提供してるところが多い。マイコミとかも当てはまる。この場合は、ライブラリのLoad(Uri)とか利用できないので、別途WebclientとかでUserAgent指定して読み込むとかかな?
RSSが壊れてる?。なんで読めないのか、時間が掛かってしまった案件のひとつ。そもそも、RSS文書が壊れてしまってる。2ch系のRSSにひとつありました。文字化けしちゃってて、その1項目だけなんだけど、item以下全部破滅状態だった。こればかりはどうしようも無い。
追記。これについて、追加したいRSSがあった。RSS文書に謎のJavaScriptがまるっと埋められてるものがありました。個人?メルマガサイトだった。<xml>定義外に書かれてましたが、これはダメですよねぇ・・。仕様的にはアリなのか?つか、感染してんのかこのサイト。と思ったら、感染サイトだったよ(;・∀・)Googleさん報告
改行コードの認識。C#でやってるとおざなりになりがちなテーマですが、これに当たる問題にも当たりました。上の問題の一つ、WebClientなどで取得したString値を、そのままXmlReader.Creat()に渡す際、このメソッドはStringでも受け取るので、これでOKと思いきや、謎のXMLパスエラー。何度見なおしてもXML文書自体は問題無い。なんとか行き着いたのが、XmlReader.Create(new StringReader(string))です。
RSS1.0のrdf:liで定義されていても、そのitemがあるとは限らない。これもある2ch系まとめRSSで見つけました。
RSS1.0のrdf:liが一切登録されず、itemだけが羅列されてる。必須項目なはずのrdf:liがまったくのゼロ定義。ちなみに、スカパJSATで見つけた。配信の意味をなさないでしょう。名の知れた会社なのにねぇ。一応、問い合わせフォームから教えてあげました。
<?xml encode=”***”>が適当。結構多かった。”utf-8”となるべきところ、”utf8”となっている。unicode以外と思って、自前でデコードしようとすると失敗する。
ちなみにXMLで記述できる文字コード指定は、IANAで規定されてるようです。
http://www.iana.org/assignments/character-sets
完璧ではないが、以下のように対処してみた。最初に100文字だけUTF-8デコードして、<?xml>内のencoding内を取得、IANAに定義があれば、それでデコードするようにします。
strXML = Encoding.GetEncoding("utf-8").GetString(bBuf, 0, 100); Match ma = Regex.Match(strXML, @"<?xml[^>]+?encoding=""(?<enc>.+?)""", RegexOptions.Singleline | RegexOptions.IgnoreCase); string strDec = "utf-8"; if (ma.Success) { EncodingInfo[] ei = Encoding.GetEncodings().Where(e => e.Name.Equals(ma.Groups["enc"].Value)).ToArray(); if (ei.Length > 0) strDec = ma.Groups["enc"].Value; }
こんな感じ、もっとありそうだなぁ・・。いまのところではRSS1.0では問題が無さそう。シンプルであるが所以か。
追記:
と思いきや、RSS1.0も出てきた。
ほとんどが、名前空間絡み。ちゃんと指定されてない場合も結構見かけます。
こちらが、アメーバブログRSSのChannel要素。
<channel xmlns="" about="http://news.ameba.jp/">
なんでか、Channelだけ名前空間指定してねぇし。普通は、デフォNS内にある。XMLパスを書く際に、TitleやらItemsとか引けなくなっちゃいます。ちなみに、Item要素も無指定。
アメーバはさらにネタを投下してくれます。日付指定がオリジナリティ溢れてます(RFC2822形式になってる)
<dc:date>Sun, 18 Sep 2011 21:00:00 JST</dc:date>
Datetimeもこのままでは認識してくれず、です。・・と思ったら、あるらしい。よく見るのが、RFC3339/W3C-DTF形式(Tで挟んで時間表記)
RFC2822の仕様
http://www.ietf.org/rfc/rfc2822.txt
W3C-DTFの仕様
http://www.w3.org/TR/NOTE-datetime
RFC3339の仕様
http://www5d.biglobe.ne.jp/~stssk/rfc/rfc3339j.html
最後の時間差表記さえ(GMTとかJPNとか+09:00とかZとか)なければ、Datetime.parse()で簡単にDateTime型に変換してくれます。
まとめサイトなんかあるんだろうか?自分で調べんの大変。
追記9/21。
とりあえず、RSS0.91の対応をしてみた。必須項目が少ないので、例外対応とするなら、簡単に実装できる。名前空間も無いし。
しかし、RSS0.90の方は、配信してるサイトを探せなかったので、確認できない。これは、無視しちゃってもいいか。