集合知、コンシューマージェネレーテッドメディア、ということをかつて言われていて、そこに興味があったので、ジモティーに関心があって、では、ちゃんと見てみよう。 という動機。
ジモティーをサイトから見ると、たくさんあるように見える。
何かが。
「件」という数字もなにかしらたくさんあるような大きな数字。
なんだろうか ? というのがまず最初に感じた疑問だった。
ここからしばらく、説明的回想シーンなので → 結論は、結果へ
しかし、よく見てみたいというのは、どんな情報であるのかというところにフォーカスが、最初はあった。それは、例えば、今日、明日、なにか交換したいとか、買いたいとか、あげたいとかのことで、その視点から Python で短いプログラムを書いた。最初は。
そして、なんかおかしい。と感じた。
それでも、ずっとジモティーを見ていた、Jupyter1 とかを使って。
- 今年の
- 今日から 41 日前の間に更新されているもの
を見るように書いたので、実行するとつらつらと写真と内容がスクロールで表示されて流れていくというものだった。
つまり比較的、売り買いする買い手にとって有効な情報とイメージされる情報にフォーカスしていた。
でも、なかなか出てこない。手頃な数字なので選んだ「3,301」という数字が書かれた「京都 (3,301)」だったが。つまり、3,301 回にわたって 1 件 1 件見ていけば、なかには古い情報もあって、非有効だけども、おおむね更新された情報であるというイメージがあったわけだが、それとは反転した結果。(数字だけ2)
そこでやはり、この「件」の数字ってなんの数字なのか ? が気になっていく。
その日(たぶん、一昨日くらい)は、とても気分が悪かった。ずっと、冴えないものを見ていたからだ。
→ 結論は、結果へ
翌日、起き上がる前に、趣向を変えて、逆の情報がいくつあるのかにフォーカスしたプログラムに書き換えてポチと実行した。クラウドの Jupyter 的なものから。
起き上がってコーヒーをいれて待っていて、たまに出力を覗いても、まだまだ終わりそうもない。
そしてまだ色気があって、昨日までの定義での「有効な情報」が出てくると写真等が表示される部分も残していたので、写真が表示されると、有効情報を掘り起こしたという査定基準になる。何から ?
当時のリアルタイムのチャットから抜粋
3,701 という数字は 3,301 の間違い invalid には別の不快な単語が記されていましたので改めました。
ここで、いったいなにが標準的というか、どういう風に「数字」や「件数」が使われるものなの ?
という足もとを確認したくなった。
「ラクマ」という言葉がジモティーに対して、シソーラスとして記憶から浮上したので、ラクマがなにか知らないが、見てみよう。
ラクマはどうやら通販サイトらしいが、中古品も扱っているようだし、ここで、イメージ的に時間軸も重要なファクターになるのが容易にイメージできる、「チケット」カテゴリを見てみる。何のためかというと、ジモティーと対照するためだ。
チケットの人気の通販できる商品の一覧です。 1,180,000点以上の購入可能なアイテムがあります。
と書かれている。ここでは、その真贋は問わないが、「購入可能な」という形容詞が数字とともに添えられている
ジモティーの場合は、ちょっと他と趣向がちがっていて、他というのは「メルカリ」等を想定しているが、ペイフォワード( pay it forward )的なシチュエーションを作り出そうとしているように感じていた。
Mark Boyle3 の著書に紹介されていた、貸し借り、ただで提供するシェアリングのエコノミーのネットワークやコミュニティに使われるインフラに近いかもしれないというように勝手に思っていて、実際にそのように使ったことがある。
この「件」の数字ってなんの数字なのか ?
当初のモチベーションから路線を変えて、数字だけにフォーカスして、数えてみる。
条件は、変えずに
- 今年の
- 今日から 41 日前の間に更新されているもの
を、反転して、この条件以外のものをカウントアップしていく。つまり、無効なもの。
また、「受付終了」とはっきりしているものも数える。
ジモティーでは、サブジェクトは
更新11月27日
作成11月27日
のように表記されている。
年月日ではないので、これは実際の日常で想定される会話で例えるとこうなると思う。
「(一昨年まえの)昨日電話したよ?」
「え?ほんと?いつ?」
「(一昨年まえの)7時頃」
「あ、気がつかなかった。ごめんね。」
不自然ではないが、ちょっとイメージが噛み合いにくくなるので、あえて省略しない方がいい場合がある。日付だけだと何らかの判断の材料にならない。
2019年 更新11月27日
2018年 作成11月27日
であっても、
更新11月27日
作成11月27日
と表示されるわけで、それを 2021 年 更新11月27日 と錯覚するのは起こり得る。
前提条件を日常に置き換えやすいように、カテゴリを「チケットの中古あげます・譲ります」と設定して、この
63615件
と表示されているものについて、いったい何のことなのか見たい。
チケットの中古あげます・譲ります 全63615件
https://jmty.jp/all/sale-tic
ジモティーの無効な( invalid )サブジェクトを数える。
これは、ジャンル /sale-tic/
の中に p-1 があり、この中に最大 57 のサブジェクトがあり、その内 50 個までがこのジャンルのなかでの投稿で、最大で 7 個が広告になっているというユニットが、p-2, p-3 , ... 最大で p-1000 までページがあるという構造のサイトで、p-1000 までのなかで存在する最大値の p-n ページから順にカウントダウン式に投稿内容を判別して、valid, invalid, adver に分けてカウントアップしていく Python プログラム。
コピーして、Jupyterlab みたいなのの Python3 カーネルを使ったノートブックのセルに貼り付けたら、そのまま実行できる。
!pip install beautifulsoup4 pycurl
from bs4 import BeautifulSoup
import re
import time,datetime
import pycurl
import certifi
from io import BytesIO
dt_now = datetime.datetime.now()
this_year = dt_now.year
day_range = 41 # limit
counter = 2
last_page = 1
kensu = 0
onece = False
invalid = 0
adver = 0 #広告
response_err = 0
#location = 'kyoto'
location = 'all'
########################################################################
while(counter > 1):
counter = counter - 1
#url = "https://jmty.jp/" + location + "/sale-inc/p-" + str(counter) #gakki
url = "https://jmty.jp/" + location + "/sale-tic/p-" + str(counter) #ticket
tempurl = re.sub(location,'prefixxxx',url)
matchtext = re.findall(r'(?<=prefixxxx).*?(?=\/p\-\d+)',tempurl)
comp_match = matchtext[0]
re.purge()
buffer = BytesIO()
c = pycurl.Curl()
c.setopt(c.CAINFO, certifi.where())
c.setopt(c.URL, url)
c.setopt(c.WRITEDATA, buffer)
c.perform()
effective_url = c.getinfo(c.EFFECTIVE_URL)
c.close()
res_byte = buffer.getvalue()
del buffer
res_text = res_byte.decode('utf-8')
del res_byte
bs = BeautifulSoup(res_text, "html.parser")
# for checking last index page
if counter == 1 and onece == False :
onece = True
try:
last_exist = bs.find("div",{"class":"last"})
if last_exist:
last_page = int(bs.find("li", {"class":"last"}).get_text())
counter = last_page + 1
except:
counter = 2
continue # go to start scraping last index page first and decriment page ,and decrement
#print(res_text)
item_box = bs.findAll("li", {"class":"p-articles-list-item"})
item_box_count = len(item_box)
print('----------------------------------------------------')
print(effective_url,end=" | ")
print(item_box_count)
print('----------------------------------------------------')
########################################################################
for i in range(item_box_count):
ended = item_box[i].find("div",{"class":"p-item-close-text"})
if ended:# 受付終了
invalid = invalid + 1
print(invalid,end=" | ")
title = item_box[i].find("div", {"class":"p-item-title"}).get_text()
title = title.strip()
price = item_box[i].find("div", {"class":"p-item-most-important"})
if price:
price = price.get_text()
price = str(price).strip()
#price = price.strip()
fav = item_box[i].find("span", {"class":"js_fav_user_count u-size-s"})
if fav :
fav = fav.get_text()
fav = fav.strip()
else:
fav = "0"
########################################################################
for ii in item_box[i].find("div", {"class":"p-item-title"}).select("a"):
#print(ii)
subject_url = ii.get("href")
if ended:
print(subject_url)
continue
valid = re.search(comp_match,subject_url)
if not valid:# 広告
re.purge()
adver = adver + 1
print(adver,end=" | ")
print(subject_url)
continue
re.purge()
buffer = BytesIO()
c = pycurl.Curl()
c.setopt(c.CAINFO, certifi.where())
c.setopt(c.URL, subject_url)
c.setopt(c.WRITEDATA, buffer)
time.sleep(1)
try:
c.perform()
except:
continue
res_code = c.getinfo(c.RESPONSE_CODE)
effective_url = c.getinfo(c.EFFECTIVE_URL)
c.close()
#print(res_code)
if res_code != 200:
response_err = response_err + 1
continue
########################################################################
res_byte = buffer.getvalue()
del buffer
html_text = res_byte.decode('utf-8')
del res_byte
#print(html_text)
bs_detail = BeautifulSoup(html_text, "html.parser")
update_date = bs_detail.find("div", {"class":"p-article-history"})
update_daytime = update_date.findAll("div")[0].get_text()
year_flag = re.search(str(this_year), update_daytime)
########################################################################
if not year_flag:
invalid = invalid + 1
print(invalid,end=" | ")
print(effective_url)
continue
else:
ymdhm = re.findall(r'\d+',update_daytime)
# year month day hour minutes
dt = datetime.datetime(int(ymdhm[0]),int(ymdhm[1]),int(ymdhm[2]),int(ymdhm[3]),int(ymdhm[4]))
re.purge()
td = dt_now - dt
if td.days > day_range:# 41 日以上前
del bs_detail
html_text = ''
invalid = invalid + 1
print(invalid,end=" | ")
print(effective_url)
continue
elif re.search("u-text-center u-size-s u-font-bold",html_text):
# 受付終了
re.purge()
html_text = ''
del bs_detail
invalid = invalid + 1
print(invalid,end=" | ")
print(effective_url)
continue
else:# 有効 #u-size-xs u-color-gray u-margin-xs-t
print("")
print("::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::")
########################################################################
kensu = kensu + 1
print(counter,kensu)
#########################################################################
del bs
print("all valid :" + str(kensu))
print("all invalid :" + str(invalid))
print("all adver :" + str(adver))# 広告
print("response_err :" + str(response_err))
結果
すべて数え上げると "all valid :"
で表示される数が、取引可能そうな件数で、もう一方 "all invalid :"
が、その反対のいわゆる有効期限切れ件数で、足し合わせた数が、なんなのか数。
all valid :1118
all invalid :48242
all adver :7000 #広告
response_err :640 #httpリクエストエラー
おかしいね。
63,615 - (1,118 + 48,242 + 640) = 13,615
最大値 1000 個までのインデックスに、1 つのインデックスに最大で 50 個までのサブジェクト(売り手の投稿記事)が詰まる(そして最大で 7 個、ジャンルと関係のない広告らしきものが入るので 1 つのインデックスに最大で 57 の投稿記事が詰められる)ので 50000件が上限なのは理解できるが、だとして自明だが、その残り(ここでは 13,615 件)はもとから見ることができない情報ということになる。
そして、有効な情報というのは 63,615 件に対して 1,118 件ということになる。
この 1,118 件というのは、表明されている 「63,615件」 と意味的に関係がない。
63,615 にこだわるならば、× 0,0176 が有効ということになるが。
2% に届かない。
関係が見えるのはむしろ invalid で 2% 以下はその誤差かもしれない。
つまり、50000 を越えている 「件」というのは、7000 件の広告と 98% 非有効な情報があり、2% 未満の有効な情報が含まれている表明であるということになる、か。
一定のペースで「件」の数が大きくなっていけば、有効な情報の比率は小さくなる方へ成長していく。
構造はだいたいわかったので、前向きに他の言語で、なんか有用なアプローチのものを考えたい。
##python
https://controlc.com/ad304bc7 言語名がパスワードですが、わざわざパスワード入れて見なくても、下記のアドレスの最後のワードを削れば画像ではなくなるはずです。そして新しいのは下記の方です。
pycurl version:
https://rentry.co/owowy/png
requests version:
https://rentry.co/z79xx
ipynb for SageMaker Studio Lab
SageMaker Studio Lab
{
"cells": [
{
"cell_type": "markdown",
"id": "220658fd-6ab7-41df-b47b-630f9da30b45",
"metadata": {},
"source": [
"%conda install beautifulsoup4 pycurl \n",
"\n",
"from bs4 import BeautifulSoup\n",
"import re\n",
"import time,datetime\n",
"\n",
"import pycurl\n",
"#import certifi\n",
"from io import BytesIO\n",
"\n",
"dt_now = datetime.datetime.now()\n",
"#this_year = dt_now.year\n",
"\n",
"counter = 2\n",
"last_page = 1\n",
"valid = 0\n",
"onece = False\n",
"invalid = 0\n",
"closed = 0\n",
"\n",
"adver = 0\n",
"response_err = 0\n",
"\n",
"location = 'kyoto'\n",
"##location = 'all'\n",
"#category = \"/sale-tic/p-\"\n",
"##category = \"/sale-tic/p-\" #ticket\n",
"##category = \"/sale-boo/p-\" #book\n",
"##category = \"/sale-pcp/p-\" #pc\n",
"##category = \"/sale-inc/p-\" #gakki\n",
"##category = \"/coop/p-\" \n",
"##category = \"/sale/p-\" #sale\n",
"##category = \"/coop-help/p-\"#tasuke\n",
"category = \"/sale-bic/p-\" #jitensya\n",
"##category = \"/est-sha/p-\" #share house\n",
"url_base = \"https://jmty.jp/\" + location + category\n",
"########################################################################\n",
"comp_match = category[0:-2]\n",
"\n",
"while(counter > 1):\n",
"\n",
" counter = counter - 1\n",
" url = url_base + str(counter)\n",
" buffer = BytesIO()\n",
" c = pycurl.Curl()\n",
" #c.setopt(c.CAINFO, certifi.where())\n",
" c.setopt(c.URL, url)\n",
" c.setopt(c.WRITEDATA, buffer)\n",
" c.perform()\n",
" effective_url = c.getinfo(c.EFFECTIVE_URL)\n",
" c.close()\n",
" res_byte = buffer.getvalue()\n",
" del buffer\n",
" res_text = res_byte.decode('utf-8')\n",
" del res_byte\n",
"\n",
" bs = BeautifulSoup(res_text, \"html.parser\")\n",
"\n",
" # for checking last index page\n",
" if counter == 1 and onece == False :\n",
" onece = True\n",
" try:\n",
" last_exist = bs.find(\"div\",{\"class\":\"last\"})\n",
" if last_exist:\n",
" last_page = int(bs.find(\"li\", {\"class\":\"last\"}).get_text())\n",
" \n",
" counter = last_page + 1\n",
" except:\n",
" counter = 2\n",
" continue # go to start scraping last index page first and decriment page ,and decrement \n",
"\n",
" #print(res_text)\n",
"\n",
" item_box = bs.findAll(\"li\", {\"class\":\"p-articles-list-item\"})\n",
" item_box_count = len(item_box)\n",
" print('----------------------------------------------------')\n",
" print(effective_url,end=\" | \")\n",
" print(item_box_count)\n",
" print('----------------------------------------------------')\n",
" \n",
"########################################################################\n",
" for i in range(item_box_count):\n",
" ended = item_box[i].find(\"div\",{\"class\":\"p-item-close-text\"}) #closed\n",
" if ended:# 受付終了\n",
" invalid = invalid + 1\n",
" closed = closed + 1\n",
" print(\"受付終了\",invalid,end=\" | \")\n",
" #check ended\n",
" check_ended = bs.find(\"div\",{\"class\":\"u-font-bold u-margin-m-t\"})\n",
" if check_ended:\n",
" check_ended_text = check_ended.get_text().strip()\n",
" #print(check_ended_text)\n",
" \n",
" title = item_box[i].find(\"h2\", {\"class\":\"p-item-title\"}).get_text()\n",
" title = title.strip()\n",
"\n",
" price = item_box[i].find(\"div\", {\"class\":\"p-item-most-important\"})\n",
" if price:\n",
" price = price.get_text()\n",
" price = str(price).strip()\n",
" price = price.strip()\n",
"\n",
" fav = item_box[i].find(\"span\", {\"class\":\"js_fav_user_count u-size-s\"})\n",
" if fav :\n",
" fav = fav.get_text()\n",
" fav = fav.strip()\n",
" else:\n",
" fav = \"0\"\n",
"########################################################################\n",
" for ii in item_box[i].find(\"h2\", {\"class\":\"p-item-title\"}).select(\"a\"):\n",
" #print(ii)\n",
" subject_url = ii.get(\"href\")\n",
" if ended:\n",
" print(subject_url)\n",
" continue\n",
"\n",
" category_match = re.findall(comp_match,subject_url)\n",
" if len(category_match) == 0:# 広告\n",
" re.purge()\n",
" adver = adver + 1\n",
" print(\"広告\",adver,end=\" | \")\n",
" print(subject_url)\n",
" print(\"::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::\")\n",
" continue\n",
" re.purge()\n",
"\n",
" buffer = BytesIO()\n",
" c = pycurl.Curl()\n",
" #c.setopt(c.CAINFO, certifi.where())\n",
" c.setopt(c.URL, subject_url)\n",
" c.setopt(c.WRITEDATA, buffer) \n",
" \n",
" #time.sleep(1)\n",
" \n",
" try:\n",
" c.perform()\n",
"\n",
" except:\n",
" continue\n",
"\n",
" res_code = c.getinfo(c.RESPONSE_CODE)\n",
" effective_url = c.getinfo(c.EFFECTIVE_URL)\n",
" c.close()\n",
" #print(res_code)\n",
" if res_code != 200:\n",
" response_err = response_err + 1\n",
" continue\n",
"########################################################################\n",
" res_byte = buffer.getvalue()\n",
" del buffer\n",
" html_text = res_byte.decode('utf-8')\n",
" del res_byte\n",
"\n",
" #print(html_text)\n",
"\n",
" bs_detail = BeautifulSoup(html_text, \"html.parser\")\n",
"\n",
" update_date = bs_detail.find(\"div\", {\"class\":\"p-article-history\"})\n",
" update_daytime = update_date.findAll(\"div\")[0].get_text()\n",
" \n",
" ymdhm = re.findall(r'\\d+',update_daytime)\n",
" # year month day hour minutes\n",
" dt = datetime.datetime(int(ymdhm[0]),int(ymdhm[1]),int(ymdhm[2]),int(ymdhm[3]),int(ymdhm[4]))\n",
" re.purge()\n",
" td = dt_now - dt\n",
" if td.days > 41:# 41 日以上前\n",
" del bs_detail\n",
" html_text = ''\n",
" invalid = invalid + 1\n",
" print(\"41日以上前\",invalid,end=\" | \")\n",
" print(effective_url)\n",
" print(\"::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::\")\n",
" continue\n",
" elif re.search(\"u-text-center u-size-s u-font-bold\",html_text):\n",
" # 受付終了\n",
" re.purge()\n",
" html_text = ''\n",
" del bs_detail\n",
" invalid = invalid + 1\n",
" closed = closed + 1\n",
" print(\"受付終了\",invalid,end=\" | \")\n",
" print(effective_url)\n",
" print(\"::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::\")\n",
" continue\n",
" else:# 有効 #u-size-xs u-color-gray u-margin-xs-t\n",
" print(\"::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::\")\n",
"#########################################################################\n",
" valid = valid + 1\n",
" print(\"p-\",counter,\"有効\",valid,effective_url)\n",
" print(title,price)\n",
" print(\"::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::\")\n",
"#########################################################################\n",
" del bs\n",
"\n",
"print(\"all 有効 :\" + str(valid))\n",
"print(\"all invalid :\" + str(invalid))\n",
"print(\"all 受付終了 :\" + str(closed))\n",
"print(\"all 広告 :\" + str(adver))\n",
"print(\"response_err :\" + str(response_err))\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4c627ff0-f57e-410c-9e3b-2dbd94190ce2",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "default:Python",
"language": "python",
"name": "conda-env-default-py"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.7"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
hy
Python では、requests というモジュールが比較的簡単に https リクエストの部分を書けるが、Curl のライブラリを python にバインドした pycurl を使って書く例をここで紹介したい。といっても、ドキュメントが丁寧に書かれているので Python ではそのまま、ドキュメントを参照するのが確かですが、hy 言語で書くと、えー ? となるので、とりあえず動くとこまでは確認したので、サンプルコードとしてリファレンスのために、hy 言語、 Python という順で並記しておいた。基本的にひとつ上に記した Python コードを hy に翻訳したものになっている。
ただ、try expect は c.setopt にたいして、うまく機能するように書けなかったので、はずしている。
Curl のバインドは Python 以外にもあり、そのどれもが書き方はおおむね似ているが、C 言語のものをもとにして他の言語にバンドしているので、どのバインドでも記述方法はやや変というようになっていると思う( C 言語の関数の表記を尊重した表記になっているため)。特に最初、Lua でのバインドを見たとき、命名規則が全く理解できなくて困ったので、Lua のコードも合わせて Lua-curl モジュールを使った例を残す。
おそらくは Curl を使ったものは、requests と比べてやや処理が速い。hy 言語では、Python と比べて速くなるのかというと、そういうわけではないです。以降のプログラムは、非同期処理にすることが可能だと思いますが、そうなっていません。hy を vim で書く場合は、こちらを参考にしてみてください。
##nim
https://controlc.com/0779aca2
##Lua
fennel
Go
-
https://mybinder.org/v2/gh/jupyter-xeus/xeus-lua/87b8701d8190e41d40117ce793e85988a0a26fc1?urlpath=lab%2Ftree%2Fnotebooks%2Fjimoty2.ipynb
どんなものを書いたかというと、あるカテゴリ、例えば「楽器」から全「件」のなかから、 ↩ -
3,301 - 3,031 ? ↩