底辺プログラマの赤裸々なQiita活動を統括するポエム記事第3弾です。
タイトルが荒ぶってきました。
去年と一昨年はこちら
まとめ
ネタ切れを危惧し投稿ハードルをさらに下げた結果、投稿数が倍増しました。1
いいね獲得は体感、難しくなってきています。
外的要因とするならば日本のエンジニアのレベルが向上しているのか、Qiitaの新レイアウトのせいなのか。
Qiitaでいいね獲得を目指すならば、技術的な一芸に秀でたプログラマになるか、一芸に秀でたポエマー表現者になる道がある気がします!
今年を振り返ってQiitaへの投稿をオススメするなら
新人プログラマ応援タグをつけているので曲りなりにも先に有益そうな個人の意見を。
ストイックに、自分が学んだこと・詰まったことの技術的解決メモを残したい人なら
いいね数には覚悟が必要かもしれません。
私も0いいねでも個人的に満足している記事はありますので、
いいねがつかない記事≠悪い記事
だと思います。
いいねがつかないと段々気持ちが沈んでいくというのは実感としてはあります。
でもだからと言って投稿をやめる理由、ローカルのメモ帳に戻る理由にはならないんじゃないかな。
やっぱりQiitaでなくても良いんですが、オンラインに書くことはそれ自体が刺激的で良いことだと思います。
(ほんとうに困っているときに助かる記事って0から十数いいねぐらいまでのものが多かったりするんですよね)
やったことの記録を取りたい人なら
ストーリーが大事だと思います。
自分の困ったこと、思ったこと、やりたかったことを読みやすく書いて、できれば画像なども矢印・テキストで加工しながら貼り付けましょう。
途中経過も大事。
あなたが感じた大切なことを書ききれれば、共感を与えられる可能性はあります。
なんだかんだで背景や物語、流れといったものがある方が記事は読みやすかったり記憶に残りやすかったりしますしね。
ITエンジニアに関するという前提は外れませんが、なぜそうしたか・そう感じたかという記録は、読み手にも振り返る未来の自分にも大事になるかもしれません。
どちらもタイトルを大事に
トレンドとタイムラインを除いて記事が最初に表示されるのは「タグフィード」と「すべての投稿」です。
主に見られるタグフィードにはいいね数などが出ないので、既に100いいねついた投稿も0いいねの投稿もタグフィード上ではタイトルに惹かれるかどうかが見てもらえる基準じゃないかなと思います。
一年目と同じことになりますが、やっぱりメモと書くのはもったいないな、と。
タグも大事に
タグフィードに乗るためにはフォローされているタグを記事につける必要があります。
補完機能を使って、できるだけ投稿数が多いタグをつけましょう。
言語やOSのバージョンなどはタグにつけると極端に減ることがあるので、その場合は本文に乗せて、タグはできるだけメジャーにしましょう。
投稿アクティビティ
毎年お世話になります。
Qiitaの投稿アクティビティをGitHubのように表示するヤツ - Qiita
下書きに溜まっているのでほぼ月曜日の朝に即投稿していました。
戦闘力
Qiitaの統計はちょくちょく出ますが、今年は統計に加え戦闘力が測れます。
リアルタイムではないですが、4月ごろのデータでナッパだそうです。ナッパは喜ばしいのか?
平均Contribution数はやっぱり寂しいですね…打率の低さが伺えます。
現在は、一記事あたり平均6Contributionなのに対し、2014年4月は34Contributionであり、**実にピーク時の5.5倍も差が開いている。**Contribution数の総和が下落傾向なのに、投稿記事数は右肩上がりなので、Contributionの獲得難易度が大きく上がっているようだ。
第一回 チキチキQiita戦闘力ランキング選手権 - Qiita
平均値と言えなくもないですが、今年の投稿に目を向けると…
一年間の投稿記事
このままでは技術的記事だという言い訳ができないので、去年作った記事一覧を出力するコードに、いいね数を追加しようと思います。
去年は想定していなかった機能なので、かなり無理やり追加しました。
var request = require('request');
// 方針
// テーブル記法の見栄え優先で、リンクは分離式でいく
// タイトルの最大文字数によってヘッダーのセパレーターや各行の余白が異なるので、最後に出力する。
// タイトルの最大長は[]を含めたものとするが、左右の見栄えのための空白は含めないものとする
// 表示は等幅フォントを想定し、長さをカウントするときは、全角文字を2とカウントする
// 全角を2とカウントする http://blog.tofu-kun.org/070627210315.php
const strLength = (strSrc) => {
var len = 0;
strSrc = escape(strSrc);
for(var i = 0; i < strSrc.length; i++, len++){
if(strSrc.charAt(i) == '%'){
if(strSrc.charAt(++i) == 'u'){
i += 3;
len++;
}
i++;
}
}
return len;
};
const createHeader = (maxTitleLength) => {
const marginLength = 2;
const dateLength = 14; // XXXX年XX月XX日(全角は2)
const likeLength = 3; // 1000いいねはありえないので(100も…まあ…)
const titleLabel = 'タイトル';
const dateLabel = '投稿日時';
const likeLabel = 'いいね';
var header =
'| ' + titleLabel + ' '.repeat(maxTitleLength - strLength(titleLabel)) +
' | ' + dateLabel + ' '.repeat(dateLength - strLength(dateLabel)) +
' | ' + likeLabel + ' |'; // ラベルのほうが長いので
var separator =
'|' + '-'.repeat(marginLength + maxTitleLength) +
'|' + '-'.repeat(marginLength + dateLength) +
'|' + '-'.repeat(marginLength + strLength(likeLabel)) + '|';
return header + '\n' + separator;
};
const createRow = (maxTitleLength, item) => {
var date = new Date(item.created_at);
date = date.getFullYear() + '年' + ('0' + (date.getMonth() + 1)).slice(-2) + '月' + ('0' + date.getDate()).slice(-2) + '日';
var padding = maxTitleLength - strLength(item.title) -2; // maxに[]は含まれているので、ここで差っ引く
return '| [' + item.title + ']' + ' '.repeat(padding) + ' | ' + date + ' | ' + item.likes_count.toString().padStart(3 + 3/* いいねが6でヘッダーのほうが長いため場当たり */) + ' |';
};
// maxTitleLengthは4以上を想定している
const createTable = (maxTitleLength, items) => {
var table = '';
table = createHeader(maxTitleLength, items);
for (var i in items) {
table = table + '\n' + createRow(maxTitleLength, items[i]);
}
return table;
};
const userId = 'khsk';
const dateFrom = '2017-05-30'; // fromは予約語かな
const dateTo = '2018-05-30';
// 48投稿前後なのでページ処理はしない
const url = 'https://qiita.com/api/v2/items?per_page=100&query=user:' + userId + '+created:>' + dateFrom + '+created:<' + dateTo;
request.get(url ,(error, response, body) => {
var obj = JSON.parse(body);
console.log(obj.length + '投稿\n');
// タイトルの最大長と分離リンクを作成するループ
var linkList = []; // リンク先のURL参照を持つ配列
var max = 0; // タイトル最大長
for (var i in obj) {
var item = obj[i];
var date = new Date(item.created_at);
max = Math.max(max, strLength(item.title));
linkList.push('[' + item.title + ']: ' + item.url);
}
max = max + 2; // タイトルを囲む[]分を加算
// 2回ループ回すの気持ち悪いなあ
var table = createTable(max, obj);
// リンク出力
linkList.forEach((e) => {
console.log(e);
});
console.log('\n');
console.log(table);
});
手動で作らずライブラリを駆使するなら
wooorm/markdown-table: Markdown tables, with alignment
などが手軽そうでいいですね。
結果
94投稿
今年の投稿記事
毎年ですが難しいことを全然していませんね。
逆に言えば、簡単なことだけでも、いち記事最高150いいね未満でも3年続ければ合計で4桁いいねまでいけるということがわかりました。
4月からの3記事でかなり平均値を助けてくれています。
普通に書くと2桁いかず、たまに伸びると50超え。とかなり振れ幅が大きいです。
それもこれもトレンドに載った影響なので、トレンドに乗らずば記事にあらず…といった気持ちもなくはないです。
うっかり100記事以上投稿するとShiiba含めページングしていないこのスクリプトも漏れがでるので、5末は逆に投稿しすぎてないか不安になりました。まさかこんな日が来るとは週一に四苦八苦していたときには露程も思わず…。
いくつか記事の振り返り
ミスって0秒で即fuck ~ 自動fuckしたいだけの人生だった
記事を書ききったときは「このタイトルしかない!」と思いましたが、冷静になると人を寄せ付けないタイトルだと気づきます。
今は美少女に罵られながらfuckしてもらっています。(反省していない。
-
コマンドを間違えるたびに美少女に罵られたい! - Qiita
fish版ありがたい!
んで、結局Contributionのうち、投稿で勝ち得たいいねはどれぐらいなのさ
それでいいのかインサート
は単純になにも考えずつけた悪い例です。
phina.jsで~シリーズ
もう一本、部屋と道を作成して移動不可のタイル(壁)を設定して徘徊できるところまで作って一応の終わりとしたんですが、タイミングを逃してその部分を投稿できてないです。
ローカルで作るとファイルを分割してrequireできるのですが、runstantに貼るときに手動で一つのファイルにするのが面倒で遠のきました。
今ゲーム系を触るならメロンも良さそうです。
ESLintのルールを全部手動で設定するのは大変だからやめておけ
最いいね数とっちゃった問題児。タグに入れてませんがやはりポエム枠ですね。
普段通り「苦労したこと(結論)→動機(目的)→経緯→参考」と書いていたら、経験上1いいねの内容だろうと思っていた内容を、直前の記事のいいね数がよかったので開き直って「逆噴射文体」で書きました。
伸びていくいいね数を眺めながら、今後全記事を(Qiitaでは)邪道と感じる「逆噴射文体」で書くか真剣に悩みました。
戸田奈津子 vs 逆噴射聡一郎
なんてワードも浮かびました。
しかし、いいねが増えて嬉しい一方、こんな記事がのびすぎるのはQiitaとして良くはないよなあと。2
ただし、普段よくないとわかりつつもどうしても使ってしまう弱い表現を廃して、断言・命令口調で書けるのは気持ちがいいので、ひっそりとまた書くかもしれません。
Contribution
フォロワーが増えましたが、ほとんどがESLintポエムと編集リクエスト活動の賜物なので、にゃんとも。
Contributionも記事のいいねが貰えないため、編集リクエスト活動で無理やり稼いでいます。
グラフは累計の推移ではなく増分になりましたね。
今年の裏テーマが1000Contribution達成だったので良かったです。
純いいね数
んで、結局Contributionのうち、投稿で勝ち得たいいねはどれぐらいなのさ
で再度見ます。
この投稿以降に編集リクエスト活動を増やしたので増分が気になります。
当時の純いいねが1月で926。当時で約50の差分と書いてます。
編集リクエスト候補の自動リストアップを3月後半からはじめて…
4ヶ月後の現在のContributionが1720。
純いいねが1465。
あっというまに200Contributionほどのいいね外のContributionが得られました。
3年で50が2ヶ月ほどで200なので、かなりの上昇率です。
1いいねの記事を書くよりは編集リクエスト書いている方が儲かるくらい。…儲かる?
印象に残っている投稿
記憶だよりなのでどうしても最近の記事になるのですが
-
カタカタカタッターンを可視化した - Qiita
ベストクソアプリ
-
もう管理画面のフロントコードを書く必要はありません、そう Viron ならね。 - Qiita
企業が作ったもの(OSSですが)をQiitaで紹介する。という流れが流行りそうだなあと思った記憶があります。
- 東大を出てゼロからプログラミングを学び、1年後にWebサービスを作ってみた話 - Qiita
- 「中年の危機」ど真ん中のオッサンがWEBサービス作ってみた。 - Qiita
- 58歳の文系エンジニアがCourseraのMachine Learningを苦労して完走した話 - Qiita
-
保育園落ちたアラフォーがプログラミングを学び、2か月でWebサービスを作った話 - Qiita
今年もたくさんのWebサービスが出来ました。最近連続で出てきた投稿群はサービス・記事の出来もさるものの、やっぱり「自分の属性」を押し出していくことが目についたポイントだなあと再確認しました。
この記事のタイトルも「自分の属性」ですしね。
-
約980名の生徒に行われたN高等学校のプログラミング教育の現状と課題 - Qiita
今年に限りませんが、N高は取り組みの姿勢がよさそうで毎回いい印象を覚えます。
-
よく使う正規表現はもうググりたくない! - Qiita
記事の出発が正しくなくても?第三者の良いコメントが寄ってきて知見が集まり投稿者が評価されるなら、間違っているかもしれなくても投稿できるようにハードルが下がる流れがくるのかな?
などと考えていた気がします。
-
VRで「結月ゆかり」になって生放送する - Qiita
Vtuberが大流行していますが、追えてないので
FaceRig → この記事 → バーチャルのじゃロリ狐娘おじさん
程度の認識しかないです。ただし美少女にはなりたい。
-
プログラムのネーミングに迷ったら GitHub でコード検索すると参考になる説 - Qiita
よくやります。辞書や翻訳と違って、「他のプログラマも使っている」という免罪符安心感が得られます。
-
無料のはずのGCEのf1-microインスタンスで11月だけ1円課金された理由 - Qiita
大量請求が来たという話はちらほら見ますが、逆に1円というところが記憶に残ります。
-
npxが結構良さそうな件について - Qiita
代わり映えしない開発環境生活ですが、npx
は明確によく使うようになったと思います。
とくにローカルにインストールしたときにパスを通さなくてもnpx パッケージ名
で実行できるので、パスを記述する手間が減りました。
来年への準備
最近、GASを学んだので、1年ごとのContributionの記録ではなく、日々の記録をスプレッドシートに取ろうかと考えています。
あいにく着手が最近で今年は間に合いませんが、1年間ブラッシュアップして、Contributionの1年間の増加を見れればと思います。
スクリーンショット
作りかけの現状です。
コード
現在作成中のコードです。
ライブラリはParser。
Easy data scraping with Google Apps Script in 5 minutes ~ kutil.org
function main() {
var spreadsheet = SpreadsheetApp.openById('~')
var userData = updateUserData()
Logger.log(spreadsheet)
Logger.log(spreadsheet.getLastRow())
var logSheet = spreadsheet.getSheetByName('ログ')
if (logSheet.getLastRow() == 0) {
initSheet(logSheet)
}
var today = Utilities.formatDate(new Date(), 'asia/tokyo', 'yyyy/MM/dd')
logSheet.appendRow([
today,
userData.items,
userData.contributions,
userData.followers,
userData.diffItems,
userData.diffContributions,
userData.diffFollowers,
userData.titles
])
// グラフ作成 todo 関数化
var graphSheet = spreadsheet.getSheetByName('グラフ')
// 毎回新規に作るので、既存のグラフは削除する(addRowで追記式のほうがスマートだろうけど)
rmAllCharts(graphSheet)
// グラフのサイズ関連がまだわかってないので今後重なったりするかも。1シート1グラフのほうがいいかなあ
// シートから作る embeddedchartbuilder では .setDataViewDefinition(viewSpec) がなく、setOptionで annotations.styleも効かせられないので、ラベルの縦書きが実現できていない。
// 今からChatr.newChartで作り直すのも面倒なので、機能拡張待ちとしたい。
var range = logSheet.getRange('A1:D' + logSheet.getLastRow())
var postTitle = logSheet.getRange('H1:H' + logSheet.getLastRow())
var chart = logSheet.newChart().addRange(range).addRange(postTitle)
.setChartType(Charts.ChartType.LINE)
.setOption('title', '累計グラフ')
.setNumHeaders(1)
.setPosition(1, 1, 0, 0)
.setOption('pointSize', 5)
.setOption('series', {
0: {
targetAxisIndex:1,
pointShape: 'circle'
},
1: {pointShape: 'circle'},
2: {
targetAxisIndex:1,
pointShape: 'circle'
}
})
.setOption('roles', {
3: {type:'annotation'}, // 記事名を注釈・特異点?化
})
.setOption('focusTarget', 'category') // 比較モード。同日の全てのツールチップ表示
.setOption('theme', 'maximized') // グラフ最大化
.build()
graphSheet.insertChart(chart)
var dateRange = logSheet.getRange('A1:A' + logSheet.getLastRow())
range = logSheet.getRange('E1:G' + logSheet.getLastRow())
chart = logSheet.newChart().addRange(dateRange).addRange(range).addRange(postTitle)
.setChartType(Charts.ChartType.LINE)
.setOption('title', '差分グラフ')
.setNumHeaders(1)
.setPosition(25, 1, 0, 0)
.setOption('pointSize', 5)
.setOption('series', {
0: {pointShape: 'circle'},
1: {pointShape: 'circle'},
2: {pointShape: 'circle'}
})
.setOption('roles', {
3: {type:'annotation'}, // 記事名を注釈・特異点?化
})
.setOption('focusTarget', 'category') // 比較モード。同日の全てのツールチップ表示
.setOption('theme', 'maximized') // グラフ最大化
.build()
graphSheet.insertChart(chart)
}
function updateUserData() {
var userId = 'khsk'
var URL = 'https://qiita.com/' + userId
var html = UrlFetchApp.fetch(URL).getContentText()
var getUserStateCount = (function(html){
return function(key) {
return Parser.data(html)
.from('/' + key + '"><span class="userActivityChart_statCount">')
.to('</span>').build()
}})(html)
var items = getUserStateCount(userId)
var contributions = getUserStateCount('contributions')
var followers = getUserStateCount('followers')
// ScriptPropertiesからとっているのは最初GAS完結していた通知スクリプトをグラフに使おうとしたので、シートからの計算をサボっている。
var ScriptProperties = PropertiesService.getScriptProperties()
var oldItems = ScriptProperties.getProperty('items') || items
var oldContributions = ScriptProperties.getProperty('contributions') || contributions
var oldFollowers = ScriptProperties.getProperty('followers') || followers
var diffItems = parseInt(items - oldItems)
var diffContributions = parseInt(contributions - oldContributions)
var diffFollowers = parseInt(followers - oldFollowers)
ScriptProperties.setProperty('items', items)
ScriptProperties.setProperty('contributions', contributions)
ScriptProperties.setProperty('followers', followers)
// key名省略はes2015だからむりー
return {
'items' : items,
'contributions' : contributions,
'followers' : followers,
'diffItems' : diffItems,
'diffContributions' : diffContributions,
'diffFollowers' : diffFollowers,
'titles' : getNewPostTitles(html, diffItems)
}
}
function initSheet(spredsheet) {
spredsheet.appendRow(['日付', '投稿数', 'Contiributions', 'フォロワー', '投稿数増分', 'Contiributions増分', 'フォロワー増分', '投稿記事'])
}
function rmAllCharts(spredsheet) {
var charts = spredsheet.getCharts()
charts.forEach(function(chart) {
spredsheet.removeChart(chart)
})
}
function getNewPostTitles(html, newCount) {
var titles = ''
var separator = '\n'
var posts = Parser.data(html)
.from('<div class="ItemLink__title">')
.to('</div>').iterate()
for(i = 0; i < newCount; ++i) {
Logger.log(posts[i])
Logger.log(posts[i].match(/">(.+)<\/a/)[1])
titles = titles + posts[i].match(/">(.+)<\/a/)[1] + separator
}
return titles.trim(separator)
}
ユーザーページのスクレイピングは以下も参考に
- Google SpreadSheet のGAS(JavaScript)でスクレイピング(Webデータゲット) - Qiita
- QiitaのContribution数が増えるたびにSlackに通知する - Qiita
今年は以上です。
お仕事がらから新しいことはあまりなく、Qiitaを書く習慣もついたのでもう最低週一投稿にあまり強くこだわってませんが、記録としては面白いのかもとも思うので、うまく行けばまた来年があるかもしれません。
-
投稿しない下書きが溜まっていることも原因です。Qiitaの方針で下書き数は増えないみたい。
https://twitter.com/htomine/status/984355071491100678 ↩ -
150いいねいかない程度、バズの範疇にも入りませんが。 ↩