今週のsakusakuというページを勝手にパースしてみました。
プログラムはPython,Java,Ruby,PHPで書き、最終的にはhttpに乗せてRSSとして「本日ないしは明日のトピック」を配信するところまでをやってみようと思っています。
今回は、ネット越しにHTMLを取得して今日・明日のコンテンツを配列にするところまでです。
Python
Pythonは2.3.5を使用しました。japanese_codecsが必要かもしれません。
HTMLのパース自体は標準のクラスを用いて行うことが可能でした。
Pythonのクラスやライブラリは思想の一貫性があり非常に推測しやすいので素敵です。
# -*- coding: UTF-8 -*-
###########################################################################
# Copyright 2005 everes.net
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
###########################################################################
from HTMLParser import HTMLParser
from datetime import date
import urllib
class SakusakuParser(HTMLParser):
sakusaku = None
def setCharset(self, encoding):
self.sakusaku = SakusakuWeek(encoding)
self.sakusaku.clearData()
def getSakusakuWeek(self):
return self.sakusaku
def handle_data(self, data):
self.sakusaku.parseData(data)
def handle_starttag(self, tag, attrs):
self.sakusaku.startTag(tag, attrs)
def handle_endtag(self, tag):
self.sakusaku.endTag(tag)
class SakusakuWeek:
weekContents = []
contentsArea = -1
divCount = 0
today = None
tmp_content = ""
def __init__(self, encoding):
self.charset = encoding
weekContents = []
def clearData(self):
weekContents = []
contentsArea = -1
divCount = 0
today = None
tmp_content = ""
def getContents(self):
return self.content
def getToday(self):
self.today = date.today()
result = self.weekContents[self.today.weekday()].split('●')
return result[1:len(result) - 1]
def getTomorrow(self):
self.today = date.today()
result = self.weekContents[self.today.weekday() + 1].split('●')
return result[1:len(result) - 1]
def parseData(self, data):
if self.isContentsArea():
data = unicode(data, self.charset).encode("UTF-8")
data = data.strip(" ¥t¥r¥n")
if data:
self.tmp_content += data
def startTag(self, tag, attrs):
if tag == 'div':
for i in range(len(attrs)):
if attrs[i][0] == 'style':
if attrs[i][1] == 'margin:10px':
self.setContentsArea(1)
self.div()
def endTag(self, tag):
if tag == 'div':
self.endDiv()
def isContentsArea(self):
return self.contentsArea == 1
def setContentsArea(self, mode):
if mode == 1:
self.contentsArea = 1
self.divCount = 0
else:
self.weekContents.append(self.tmp_content)
self.tmp_content = ""
self.contentsArea = -1
def div(self):
if self.isContentsArea():
self.divCount += 1
def endDiv(self):
if self.isContentsArea():
self.divCount -= 1
if self.divCount == 0:
self.setContentsArea(-1)
class Sakusaku:
sakusaku = None
def feed(self):
url = "http://www.tvk-yokohama.com/saku2/week.html"
data = urllib.urlopen( url )
charset = data.headers.getparam('charset')
if charset == None:
charset = 'japanese.shift_jis'
parser = SakusakuParser()
parser.setCharset(charset)
parser.feed( data.read() )
self.sakusaku = parser.getSakusakuWeek()
def getToday(self):
self.feed()
return self.sakusaku.getToday()
def getTomorrow(self):
self.feed()
return self.sakusaku.getTomorrow()
def main():
sakusaku = Sakusaku()
try:
content = sakusaku.getToday()
except IndexError,e:
content = ["None","Holiday"]
for i in range(len(content)):
print ' %s' % content[i]
if __name__ == "__main__":
main()
JAVA
Javaも標準のライブラリのみで実装が可能です(swingパッケージですが)。
LLな言語ではないのですが、普段仕事で使用しているのでつい作ってしまいました。
仕事で使っていてこのコードは恥ずかしいのですが、もうJavaはおもしろくはないので手抜きです。
/**************************************************************************
Copyright 2005 everes.net
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
**************************************************************************/
package net.everes.junk;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.parser.DTD;
import javax.swing.text.html.parser.DocumentParser;
import javax.swing.text.html.parser.TagElement;
import javax.swing.text.html.parser.ParserDelegator;
import javax.swing.text.MutableAttributeSet;
public class SakusakuCallback extends HTMLEditorKit.ParserCallback {
List weekContents;
boolean contentsArea;
int today;
int tomorrow;
String tmpContent;
int divCount = 0;
public SakusakuCallback() {
super();
weekContents = new ArrayList();
contentsArea = false;
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
today = calendar.get(Calendar.DAY_OF_WEEK) - 2;
tomorrow = calendar.get(Calendar.DAY_OF_WEEK) - 1;
tmpContent = "";
}
public void handleText(char[] data, int pos) {
if(contentsArea) {
tmpContent += new String(data);
}
}
public void handleStartTag(HTML.Tag tag, MutableAttributeSet attrs, int pos) {
if(tag.equals(HTML.Tag.DIV)) {
if(!contentsArea) {
Enumeration enumeration = attrs.getAttributeNames();
while(enumeration.hasMoreElements()) {
Object obj = enumeration.nextElement();
String name = "" + obj;
if(name.equals("style")) {
Object value = attrs.getAttribute(obj);
if(value.toString().equals("margin:10px")) {
contentsArea = true;
divCount = 0;
}
}
}
}
findDiv();
}
}
public void handleEndTag(HTML.Tag tag, int pos) {
if(tag.equals(HTML.Tag.DIV)) {
findEndDiv();
}
}
public String[] getToday() throws ArrayIndexOutOfBoundsException {
String tmp = weekContents.get(today).replaceAll(" ¥t¥r¥n","");
String[] result = tmp.split("●");
String[] contents = new String[result.length];
for(int i = 0;i < result.length;i++) {
contents[i] = result[i];
}
return contents;
}
private void findDiv() {
if(contentsArea) {
divCount += 1;
}
}
private void findEndDiv() {
if(contentsArea) {
divCount -= 1;
}
if(contentsArea) {
if(divCount == 0) {
contentsArea = false;
weekContents.add(tmpContent);
tmpContent = "";
}
}
}
public List getContents() {
return this.weekContents;
}
public static void main(String[] args) {
try {
URL url = new URL("http", "www.tvk-yokohama.com", "/saku2/week.html");
HttpURLConnection request = (HttpURLConnection) url.openConnection();
ParserDelegator parser = new ParserDelegator();
HTMLEditorKit.ParserCallback sakusaku = new SakusakuCallback();
parser.parse(new InputStreamReader(request.getInputStream()), sakusaku, true);
String[] result = ((SakusakuCallback)sakusaku).getToday();
for(String day: result) {
System.out.println(day);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
RUBY
現場の若いのにやるように進めながら先に作ってしまいました(シャアサクくんは見ないでね)。
残念ながらRAAというライブラリ群にあったHTMLパーサでは「今週のサクサク」はパースできませんでした。アトリビュートがクォートされていないとこけてしまいます。
いい感じに動くHTMLパーサがありましたので、それを使ってやってみました。
#ただし、パーサはストリームを求めているのにHTTPでストリームを返すやり方が発見できず。。。ま、仕方ないですが。
Rubyでおもしろかったのは、無名クラスの作り方です。
下記のコードではパーサのクラスを継承して作っていますが、Ruby的にはクラスをインスタンス化してからインスタンスのメソッドを書き換えることができるようです。
実際には恐ろしくて使えませんが、Rubyはやばそうだと思わせるに十分でした。
###########################################################################
# Copyright 2005 everes.net
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
###########################################################################
require 'simplehtmlparse' #http://arika.org/archive/?C=M;O=A
require 'net/http'
require 'date'
require 'nkf'
class Sakusaku < SimpleHtmlParse
weekContents = Array.new
contentsArea = 0
divCount = 0
tmpString = ""
def initialize(io)
super(io)
@weekContents = Array.new
@divCount = 0
@tmpString = ""
end
def begin_tag(name, attribute, orig_text)
if(name == "DIV")
if(attribute.has_key? "STYLE")
if(attribute["STYLE"] == "margin:10px")
@contentsArea = 1
@divCount = 0
end
end
if(@contentsArea == 1)
@divCount += 1
end
end
end
def end_tag(name, attribute)
if(@contentsArea == 1)
if(name == "DIV")
@divCount -= 1
if(@divCount == 0)
@weekContents << @tmpString
@tmpString = ""
@contentsArea = 0
end
end
end
end
def text(orig_text)
if(@contentsArea == 1)
tmp = orig_text.chomp.gsub(/¥t¥s /,'')
if(tmp != '')
@tmpString << orig_text.gsub(/¥t¥s/,'')
end
end
end
def getWeekContents()
return @weekContents
end
def getToday()
dt = Date::today
today = dt.wday - 1
return getContents(today)
end
def getTomorrow()
dt = Date::today
tomorrow = dt.wday
return getContents(tomorrow)
end
def getContents(dayofweek)
tmp = @weekContents[dayofweek].split('●')
tmp.delete_at(0)
return tmp
end
end
Net::HTTP.version_1_2
body = Net::HTTP.get('www.tvk-yokohama.com', '/saku2/week.html', 80)
f = open("week_tmp.html","w+")
f.puts(NKF.nkf('-w',body))
f.close()
f = open("week_tmp.html")
sakusaku = Sakusaku.new(f)
sakusaku.parse
f.close()
begin
result = sakusaku.getTomorrow
result.each {|x| puts x}
rescue
puts "holiday"
end
PHP
phpは挫折しました。
標準のXML関数はHTMLのパースができません(XHTMLならできる)。
仕方なくごちゃごちゃなPEARも見てみましたが、標準と同様でした(そのあたりの思想の欠如もphpが好きでない理由です)。
世の中にライブラリも見あたりません。
phpのHTMLパーサは、知人が作っている(まだ未公開)ので作りたくありません。
知人はあっという間にphpでサクサクパーサを作りました。
結論としてphp(というか言語を取り巻く環境)は嫌いです。
以下、書き途中でがっかりしたコード。
// php 挫折(;o;) phpのXML関数ではXHTML以外はパースできず、他に良さそうなパーサも見つからず。。。
function beginTag($psr, $name, $attributes) {
print "start" . $name . "¥n";
}
function endTag($psr, $name) {
print "end:" . $name . "¥n";
}
function text($psr, $data) {
print $data;
}
function getSakusakuData() {
$URL = "http://www.tvk-yokohama.com/saku2/week.html";
$buff = "";
$fp = fopen($URL,"r");
while ( !feof($fp) ) {
$buff .= fgets($fp,4096);
}
fclose($fp);
$buff = mb_convert_encoding($buff, "UTF-8", "Shift_JIS");
return $buff;
}
$data = getSakusakuData();
$parser = xml_parser_create();
xml_set_element_handler($parser, "begintag", "endTag");
xml_set_character_data_handler($parser, "text");
xml_parser_set_option($parser,XML_OPTION_SKIP_WHITE,1);
xml_parse($parser, $data);
xml_parser_free($parser);