【開発】.net C#でRSS/Atom feedとか読んでみる

2011年9月16日開発ざれごとatom,c#,rss2.0

そういや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の方は、配信してるサイトを探せなかったので、確認できない。これは、無視しちゃってもいいか。

2011年9月16日開発ざれごとatom,c#,rss2.0

Posted by nabe