読者です 読者をやめる 読者になる 読者になる

のらぬこの日常を描く

ノージャンルのお役立ち情報やアニメとゲームの話、ソフトウェア開発に関する話などを中心としたブログです。

楽天市場のジャンル一覧を取得する

技術・開発

前説

はてなダイアリーの編集ツールってぶっちゃけ使いづらいし重いしもうちょっと何とかして欲しいところです。

さて、なんとなくその気になったので楽天のジャンル一覧を取得するコードを書いてみました。JAVAで*1

楽天市場の商品って、ジャンルごとに割と細かく分類されているのですが、全ジャンル一覧みたいなのががどうも楽天サイトのどこにも無さそうなのでちょいと作ってみたってところです。

コードとか載せてみる

えーっと、楽天で定義されている商品ジャンルって実は45000位ありました。下記のコードを実行すると楽天APIに45000回のGETリクエストを一気に投げます。

みんなが一斉にやると楽天に怒られるかもしれないので注意です。

pom.xml
<dependency>
	<groupId>net.arnx</groupId>
	<artifactId>jsonic</artifactId>
	<version>1.3.0</version>
</dependency>
<dependency>
	<groupId>org.apache.httpcomponents</groupId>
	<artifactId>httpclient</artifactId>
	<version>4.2.4</version>
</dependency>
<dependency>
	<groupId>org.apache.commons</groupId>
	<artifactId>commons-lang3</artifactId>
	<version>3.1</version>
</dependency>
Base.java
public abstract class Base {
	protected HttpClient client;
	Base() {
		client = new DefaultHttpClient();
		client.getParams().setParameter(ClientPNames.COOKIE_POLICY,
				CookiePolicy.BROWSER_COMPATIBILITY);
		client.getParams().setParameter("http.connection.timeout", 5000);
		client.getParams().setParameter("http.socket.timeout", 3000);
	}
}
CategoryListCreator.java
public class CategoryListCreator extends Base
{
	public static void main(String[] args) throws Exception
	{
		Collection<CategoryItem2> categories = new CategoryListCreator().enumSubCategories(null);

		FileSystem fs = FileSystems.getDefault();
		Path output = fs.getPath("c:/rakuten.csv");
		Files.write(output, CollectionUtils.transform(categories,
				new CollectionUtils.Transformer<CategoryItem2, String>() {
					@Override
					public String transform(CategoryItem2 source) {
						return String.format("%d,%d,%s", source.getGenreId(), source.getParentGenreId(),
								source.getGenreName());
					}
				}), Charset.forName("utf-8"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
	}

	public List<CategoryItem2> enumSubCategories(CategoryItem2 parent) throws ParseException, IOException,
			InterruptedException {
		int genreId = parent == null ? 0 : parent.getGenreId();
		List<CategoryItem2> result = new ArrayList<>();
		if (parent != null) {
			result.add(parent);
		}
		String url = Constant.Url.Rakuten.getCategorySearch2Url(genreId);

		HttpGet g = new HttpGet(url);
		HttpResponse response = client.execute(g);
		HttpEntity entity = response.getEntity();
		if (entity != null) {
			String s = EntityUtils.toString(entity);
			CategorySearch2 item = JSON.decode(s, CategorySearch2.class);
			for (CategoryChildItem2 child : item.getChildren()) {
				CategoryItem2 childItem = child.getChild();
				childItem.setParentGenreId(parent != null ? parent.getGenreId() : -1);
				result.addAll(enumSubCategories(childItem));
			}
		}
		return result;

	}
}
Constant.java
package com.noranuko.rakuten.batch.util;

import com.noranuko.rakuten.batch.pojo.ItemSearchParameter;

public class Constant {
	static public class Url {
		static public class Rakuten {
			private static final String CATEGORY_SEARCH_2 = "https://app.rakuten.co.jp/services/api/IchibaGenre/Search/20120723?genreId=%d&genrePath=%d";
			private static final String ITEM_SEARCH_2 = "https://app.rakuten.co.jp/services/api/IchibaItem/Search/20130424?";

			private static String getCommonParameter() {
				return String.format("applicationId=%s&affiliateId=%s&format=%s", CertKey.Rakuten.DEVELOPER_ID, CertKey.Rakuten.AFFILIATE_ID, "json");
			}

			public static String getCategorySearch2Url(int genreId) {
				return String.format(CATEGORY_SEARCH_2, genreId, 0) + "&" + Constant.Url.Rakuten.getCommonParameter();
			}
		}

	}

	static public class CertKey {
		static public class Rakuten {
			public final static String DEVELOPER_ID = "********";

			public final static String AFFILIATE_ID = "********";
		}
	}
}
CategorySearch2.java
public class CategorySearch2 {
	/** getter と setter は省略 **/
	public static class CategoryChildItem2{
		private CategoryItem2 child;
	}
	public static class CategoryItem2 {
		private int parentGenreId;
		private int genreId;
		private String genreName;
		private int genreLevel;
	}
	private List<String> parents;
	private CategoryItem2 current;
	private List<CategoryChildItem2> children;
}
CollectionUtils.java
public class CollectionUtils {
	public static interface Transformer<S,T>{
		public T transform(S source);
	}

	public static <S,T>Collection<T> transform(Collection<S> source, Transformer<S, T> transformer) {
		Collection<T> result = new ArrayList<T>();

		for(S item : source) {
			result.add(transformer.transform(item));
		}
		return result;
	}
}

解説とかしてみる

http://webservice.rakuten.co.jp/api/ichibagenresearch/ - 楽天ジャンル検索API2 を読むと、 https://app.rakuten.co.jp/services/api/IchibaGenre/Search/20120723?[parameter]=[value]… なリクエストを投げると、json or xml で結果を返してくれるってことがわかります。

取り敢えず、今回はjsonで返ってきた結果を jsonic を使用して一旦pojo化して、最後にcsvファイルとして書き出しています。

実際に実行して返ってくる jsonオブジェクトは ↓な感じです。

{
    "children": [
        {
            "child": {
                "genreId": 213192, 
                "genreLevel": 4, 
                "genreName": "東芝"
            }
        }, 
        {
            "child": {
                "genreId": 213191, 
                "genreLevel": 4, 
                "genreName": "ソニー"
            }
        }, 
        {
            "child": {
                "genreId": 100178, 
                "genreLevel": 4, 
                "genreName": "その他"
            }
        }, 
        {
            "child": {
                "genreId": 213193, 
                "genreLevel": 4, 
                "genreName": "パナソニック"
            }
        }, 
        {
            "child": {
                "genreId": 213190, 
                "genreLevel": 4, 
                "genreName": "アイワ"
            }
        }
    ], 
    "current": {
        "genreId": 213189, 
        "genreLevel": 3, 
        "genreName": "ラジオ"
    }, 
    "parents": [
        {
            "parent": {
                "genreId": 100155, 
                "genreLevel": 2, 
                "genreName": "オーディオ"
            }
        }
    ]
}

children 要素の下に更に child 要素があったりしてちょっと冗長なのが、pojoの設計にモロに影響していてちょっと嫌な感じです。

mainメソッドの最後にある、FileSystem とか Path とか Files class は java7で追加されたファイル操作クラス群です。java6までと比べると、ファイル書き込みオブジェクトの生成が非常にわかりやすくなったとおもいます。

これにより、今までは、ファイル書き込みオブジェクトを初期化するためにBufferedWriterだのOutputStreamWriter などをずらっとつなげて記載するキ※ガイじみた呪文を唱えなければいけなかったのが、JAVAにしては比較的シンプルに書けるようになったのは素直に喜ぶべきところでしょうか。

*1C++は数年まともに買いてなくてもしっかり覚えてるんですが、scalaは1年書かないと凄い忘れて困ります。