「parse」を含む日記 RSS

はてなキーワード: parseとは

2025-06-13

我が名はサイボーグdorawii

パーマリンク署名対象にするより堅牢自動化を作れた。

一度投稿したうえで別タブを開いてプログラム的(fetch)に送信してその別タブが閉じられる仕組み。

改めてスクリプト配布しちゃる

最初投稿してエントリページに移動した親タブ側のjsコード
// ==UserScript==
      // @name         PGP署名検出と別タブ自動編集
      // @namespace    http://tampermonkey.net/
      // @version      1.0
      // @description  PGP署名がない投稿自動編集ページへ誘導
      // @match        https://anond.hatelabo.jp/*
      // @grant        GM_setValue
      // @grant        GM_getValue
      // @grant        GM.openInTab
      // ==/UserScript==

      (function () {
        'use strict';

        const body = document.getElementById('entry-page');
        if (!body) return;

        const titleText = document.title;
        if (!titleText.includes('dorawii')) return;

        const pgpRegex = /BEGIN.*PGP(?: SIGNED MESSAGE| SIGNATURE)?/;
        const preElements = document.querySelectorAll('div.body pre');
        let hasPgpSignature = false;

        for (const pre of preElements) {
          if (pgpRegex.test(pre.textContent)) {
            hasPgpSignature = true;
            break;
          }
        }

        if (hasPgpSignature) return;

        const editLink = document.querySelector('a.edit');
        const childTab = GM.openInTab(editLink.href, { active: false, insert: true, setParent: true });

      })();
親タブから開かれる編集ページの子タブのjsコード
 // ==UserScript==
      // @name         編集ページ処理と自動送信・閉じ
      // @namespace    http://tampermonkey.net/
      // @version      1.0
      // @description  編集ページで署名処理と送信、タブ自動閉じ
      // @match        https://anond.hatelabo.jp/dorawii_31/edit?id=*
      // @grant        GM_getValue
      // @grant        GM_xmlhttpRequest
      // @grant        GM_setClipboard
      // @grant        GM_notification
      // @connect      localhost
      // ==/UserScript==

      (async function () {
        'use strict';

        const shouldRun = await GM_getValue('open-tab-for-edit', '0');

        const textareaId = 'text-body';
        const textarea = document.getElementById(textareaId);

        if (!textarea) return;

        const content = textarea.value;

        const pgpSignatureRegex = /-----BEGIN PGP SIGNED MESSAGE-----[\s\S]+?-----BEGIN PGP SIGNATURE-----[\s\S]+?-----END PGP SIGNATURE-----/;
        if (pgpSignatureRegex.test(content)) {
          console.log('[PGPスクリプト] 署名が検出されたためそのまま送信します');
          return;
        }

        const httpRequest = (url, data) => {
          return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
              method: 'POST',
              url: url,
              headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
              data: `value=${encodeURIComponent(data)}`,
              onload: function (response) {
                resolve(response.responseText);
              },
              onerror: function (error) {
                reject(error);
              }
            });
          });
        };


        // textarea の値を取得
        // 1. 現在のページのURLからURLオブジェクト作成
        const currentUrl = new URL(window.location.href);

        // 2. ベースとなる部分 (例: "https://anond.hatelabo.jp") を取得
        const origin = currentUrl.origin;

        // 3. 'id' パラメータの値 (例: "20250610184705") を取得
        const idValue = currentUrl.searchParams.get('id');

        // 4. ベース部分とIDを結合して、目的URL文字列を生成
        //    idValueが取得できた場合のみ実行する
        let newUrl = null;
        if (idValue) {
          newUrl = `${origin}/${idValue}`;
        }

        // 5. 生成されたURL変数に代入し、コンソールに出力して確認
        console.log(newUrl);
        const valueToSend = newUrl;

        try {
          const signatureText = await httpRequest('http://localhost:12345/run-batch', valueToSend);
          console.log('バッチ応答:', signatureText);
          if (!signatureText.includes('BEGIN PGP SIGNED MESSAGE')) {
            alert('PGP署名クリップボードに見つかりませんでした。');
            return;
          }

          const newText = content.replace(/\s*$/, '') + '\n' + signatureText + '\n';
          textarea.value = newText;

          console.log('[PGPスクリプト] 署名を貼り付けました。送信を再開します。');


          const form = document.forms.edit;

          const newForm = form.cloneNode(true);
          form.replaceWith(newForm);

          newForm.addEventListener('submit', async (e) => {
            e.preventDefault(); // HTML標準のsubmitをキャンセル
            const bodyText = textarea?.value || '';

            // reCAPTCHA トークンの取得
            const recaptchaToken = await new Promise((resolve) => {
              grecaptcha.enterprise.ready(() => {
                grecaptcha.enterprise.execute('hoge', { action: 'EDIT' })
                  .then(resolve);
              });
            });

            // POSTするデータの構築
            const formData = new FormData(newForm);
            formData.set('body', bodyText);
            formData.set('recaptcha_token', recaptchaToken);
            formData.set('edit', '1');
            try {
              const response = await fetch(newForm.action, {
                method: 'POST',
                body: formData,
                credentials: 'same-origin'
              });


              if (response.ok) {
                console.log('送信成功');
                window.close();


              } else {
                console.error('送信失敗', response.status);
              }
            } catch (err) {
              console.error('送信中にエラーが発生', err);
            }

          });

          // プログラム的に送信トリガー
          newForm.dispatchEvent(new Event('submit', { bubbles: true }));

        } catch (e) {
          console.error('バッチ呼び出し失敗:', e);
        }

      })();
node.jsで動かすローカルサーバーコード
const http = require('http');
const { exec } = require('child_process');
const querystring = require('querystring');

const server = http.createServer((req, res) => {
  if (req.method === 'GET' && req.url === '/ping') {
    res.writeHead(200);
    res.end('pong');
  } else if (req.method === 'POST' && req.url === '/run-batch') {
    let body = '';

    req.on('data', chunk => {
      body += chunk.toString();
    });

    req.on('end', () => {
      const parsed = querystring.parse(body);
      const value = parsed.value || 'default';

      // 値を引数としてバッチに渡す
      exec(`C:\\Users\\hoge\\Desktop\\makesign.bat "${value}"`, { encoding: 'utf8' }, (err, stdout, stderr) => {
        if (err) {
          res.writeHead(500);
          res.end('Error executing batch: ' + stderr);
        } else {
          res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
          res.end(stdout.trim());
        }
      });
    });

  } else {
    res.writeHead(404);
    res.end('Not found');
  }
});

server.listen(12345, () => {
  console.log('Batch server running at http://localhost:12345/');
});
@echo off
setlocal enabledelayedexpansion


:: 署名するファイルset "infile=%~1"
set outfile=%TEMP%\pgp_output.asc

:: 以前の出力があれば削除
if exist "%outfile%" del "%outfile%"


:signloop
:: AutoHotkeyパスフレーズ入力(gpgがパスワード要求するダイアログが出た場合に備える)
start "" /b "C:\Users\hoge\Documents\AutoHotkey\autopass.ahk"

:: PGPクリア署名作成
echo %infile% | gpg --yes --clearsign --output "%outfile%"


:: 署名成功していればループを抜ける
if exist "%outfile%" (

    goto postprocess
) else (

    timeout /t 1 > nul
    goto signloop
)
:postprocess

powershell -nologo -command ^
  "$header = '>|'; $footer = '|<'; $body = Get-Content '%outfile%' -Raw; Write-Output ($header + \"`r`n\" + $body + $footer)"

powershell -nologo -command ^
  "$header = '>|'; $footer = '|<'; $body = Get-Content 'signed.asc' -Raw; Set-Clipboard -Value ($header + \"`r`n\" + $body + $footer)"

endlocal
exit /b
AutoHotkey(以前と同じ)
#Persistent
#SingleInstance ignore
SetTitleMatchMode, 2
WinWaitActive, pinentry
SendInput password
Sleep 100
SendInput {Enter}
ExitApp

動けばいいという考えで作っているので余分なコードも含んでいるかもしれない。

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

https://anond.hatelabo.jp/20250613185036 
-----BEGIN PGP SIGNATURE-----

iHUEARYKAB0WIQTEe8eLwpVRSViDKR5wMdsubs4+SAUCaEv1FQAKCRBwMdsubs4+
SHHkAQDUOLgBcdji2T6MJ7h/vlMdFfGlWAzNdXijjE1gIuEPywEAiMNMZqhrMmtl
c7UqRuggNJ/UTa5xTIcKp622+7jJQQg=
=Lgkl
-----END PGP SIGNATURE-----

2025-06-09

dorawii

ようやく(ほぼ)すべてが自動化された。

あとはローカルサーバーの起動をスタートアップに設定する(方法AIに聞いて指示に従う)だけの消化試合

ここにほとんどAI頼りのコードを公開しておく。

事前にインストールしておくもの

autohotkey

nodejs

ユーザースクリプトを実行できる拡張機能

パスとかの注意

署名要求してくるパスワードを自動入力するahkファイルドキュメントAutoHotkey配下に置いた。

バッチファイル(make.sign.bat)はデスクトップに置いた。

以下コード

autopass.ahk
#Persistent
#SingleInstance ignore
SetTitleMatchMode, 2
WinWaitActive, pinentry
SendInput お前のパスワード
Sleep 100
SendInput {Enter}
ExitApp
run-bacth-server.js
// run-batch-server.js
const http = require('http');
const { exec } = require('child_process');

const server = http.createServer((req, res) => {
  if (req.url === '/ping') {
    res.writeHead(200);
    res.end('pong');
  } else if (req.url === '/run-batch') {
    exec('C:\\Users\\you\\Desktop\\makesign.bat', (err) => {
      res.writeHead(200);
      res.end(err ? 'Error' : 'OK');
    })
    ;
  } else {
    res.writeHead(404);
    res.end('Not found');
  }
});

server.listen(12345, () => {
  console.log('Batch server running at http://localhost:12345/');
});
makesign.bat
@echo off
setlocal enabledelayedexpansion

:: ミリ秒単位UTC時刻を取得
for /f %%a in ('powershell -nologo -command "[int64]::Parse((Get-Date).ToUniversalTime().ToString('yyyyMMddHHmmssfff'))"') do set timestamp=%%a

:: 署名するファイルset infile=%TEMP%\pgp_input.txt
set outfile=%TEMP%\pgp_output.asc

:: 以前の出力があれば削除
if exist "%outfile%" del "%outfile%"

:: タイムスタンプを原文として保存
echo %timestamp% > "%infile%"

:signloop
:: AutoHotkeyパスフレーズ入力(gpgがパスワード要求するダイアログが出た場合に備える)
start "" /b "C:\Users\infini\Documents\AutoHotkey\autopass.ahk"

:: PGPクリア署名作成
gpg --yes --clearsign --output "%outfile%" "%infile%"


:: 署名成功していればループを抜ける
if exist "%outfile%" (
    echo [INFO] 署名成功
    goto postprocess
) else (
    echo [WARN] 署名失敗、再試行します…
    timeout /t 1 > nul
    goto signloop
)
:postprocess

:: PowerShellで余計な改行なしに |< をつけてクリップボードコピー
powershell -nologo -command ^
  "$header = '>|'; $footer = '|<'; $body = Get-Content '%outfile%' -Raw; Set-Clipboard -Value ($header + \"`r`n\" + $body + $footer)"

echo Done. signed.asc created and clipboard updated (no extra blank line).
endlocal
exit /b
tempermonkeyとかに登録するユーザースクリプト
// ==UserScript==
// @name         PGP署名自動付加スクリプト(GM_xmlhttpRequest版)
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  投稿前にPGP署名を付けてから送信(fetch未使用)
// @match        https://anond.hatelabo.jp/dorawii_31/edit*
// @grant        GM_xmlhttpRequest
// @grant        GM_setClipboard
// @grant        GM_notification
// / @connect      localhost
// ==/UserScript==

(function () {
  'use strict';

  const submitId = 'submit-button';
  const textareaId = 'text-body';
  const localServer = 'http://localhost:12345/run-batch';

  const pgpSignatureRegex = /-----BEGIN PGP SIGNED MESSAGE-----[\s\S]+?-----BEGIN PGP SIGNATURE-----[\s\S]+?-----END PGP SIGNATURE-----/;

  const httpRequest = (url) => {
    return new Promise((resolve, reject) => {
      GM_xmlhttpRequest({
        method: 'GET',
        url: url,
        onload: function (response) {
          resolve(response.responseText);
        },
        onerror: function (error) {
          reject(error);
        }
      });
    });
  };

  const interceptClick = () => {
    const btn = document.getElementById(submitId);
    if (!btn || btn.dataset.pgpIntercepted === 'true') return;
    btn.dataset.pgpIntercepted = 'true';

    btn.addEventListener('click', async function (e) {
      const textarea = document.getElementById(textareaId);
      if (!textarea) return;

      const content = textarea.value;

      if (pgpSignatureRegex.test(content)) {
        console.log('[PGPスクリプト] 署名が検出されたためそのまま送信します');
        return;
      }

      e.preventDefault();
      e.stopImmediatePropagation();
      console.log('[PGPスクリプト] 署名が見つからないため処理を停止し、署名を取得します');

      try {
        await httpRequest(localServer); // バッチ実行

        const signatureText = await navigator.clipboard.readText();
        if (!signatureText.includes('BEGIN PGP SIGNED MESSAGE')) {
          alert('PGP署名クリップボードに見つかりませんでした。');
          return;
        }

        const newText = content.replace(/\s*$/, '') + '\n' + signatureText + '\n';
        textarea.value = newText;

        console.log('[PGPスクリプト] 署名を貼り付けました。送信を再開します。');
        btn.click(); // イベント再発火

      } catch (err) {
        alert('PGP署名の取得または貼り付けに失敗しました。\n' + err);
      }
    }, true);
  };

  window.addEventListener('load', () => {
    setTimeout(interceptClick, 1000);
  });
})();

プロミスメソッドとか全然まだ理解してなくてそのなかに関数代入したその関数オブジェクトプロパティresponseを?いやまあそのあたりのコードが示すデータの流れが全然理解できないような人間でもここまでできちゃった。

AIすごいなと思うよ。そして思うのは今後重要になってくるのは文法とか自体に詳しいことじゃなくて、そのプログラムの処理内容を指示できるシステムエンジニア的な言語化能力のほうじゃないかなと思った。

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

20250609111559680 
-----BEGIN PGP SIGNATURE-----

iHUEARYKAB0WIQTEe8eLwpVRSViDKR5wMdsubs4+SAUCaEbCbwAKCRBwMdsubs4+
SLueAPwOv7PBk4voAe5qlcCEvs/PJhmKc5QAb/1R43JMQFuDZgD/UTPEKsL/PhK9
jFGv2HDXK1dVjLNwvosgX9uYJh5xxwY=
=qiOE
-----END PGP SIGNATURE-----

2025-06-08

dorawii

ChatGPTにバッチファイルを作ってもらったのでこれから署名捗るぞ。これだけ手軽化できたらレスバに入っても署名つけるのも億劫にならずできそうだ。

なにせ文章を書き折ったらあとはバッチダブルクリックしてCtr+Vするだけだ。

原文には現在日時のミリ秒表示を使うことにした。

名乗る人が増えることを期待して作らせたものを公開しておく。

@echo off
setlocal

:: ミリ秒単位UTC時刻を取得
for /f %%A in ('powershell -nologo -command "[int64]::Parse((Get-Date).ToUniversalTime().ToString('yyyyMMddHHmmssfff'))"') do set timestamp=%%A

:: PGPクリア署名作成
echo %timestamp% | gpg --yes --clearsign > signed.asc

:: PowerShellで余計な改行なしに |< をつけてクリップボードコピー
powershell -nologo -command ^
  "$header = '>|'; $footer = '|<'; $body = Get-Content 'signed.asc' -Raw; Set-Clipboard -Value ($header + \"`r`n\" + $body + $footer)"

echo Done. signed.asc created and clipboard updated (no extra blank line).
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

20250608045542542 
-----BEGIN PGP SIGNATURE-----

iHUEARYKAB0WIQTEe8eLwpVRSViDKR5wMdsubs4+SAUCaEUXzgAKCRBwMdsubs4+
SCvuAQDjRjPVCO1F9DgmAKoqKYG7qB9r4e7y2Ky+/umT/OhnygEA8h2NL8zIczSp
hcOk/MaDWJJ2Y7WDA3d6gxFakW8TKAw=
=HE4/
-----END PGP SIGNATURE-----

2025-05-18

官報ダウンロード

令和7年4月1日以降、官報帰化情報が90日経過で閲覧不可になった。

プライバシー配慮」とのことだが、最近の不自然戸籍不要発言などと合わせて考えると嫌な予感しかしない。

そこでとりあえず官報を保存できるプログラムを作った。自分ダウンロードして保存すること自体は全く問題ない行為

官報は平日の8:30に公開されるので、cronで8:31かに実行すると良いのでは。

# 官報PDFデータを入手して保存する
import requests
import os
import time
from bs4 import BeautifulSoup
from urllib.parse import urljoin

# 対象URL
index_url = "https://www.kanpo.go.jp/index.html"
base_url = 'https://www.kanpo.go.jp/'

# ダウンロードフォルダ
download_dir = 'pdfs'
os.makedirs(download_dir, exist_ok=True)

# ページ取得
response = requests.get(index_url)
response.encoding = 'utf-8'
text = response.text

# HTMLを解析
soup = BeautifulSoup(text, "html.parser")
results = []

# 「本日官報」を対象PDF情報を取得する
today_box = soup.find('div', class_='todayBox')
if today_box:
    dl = today_box.find('dl')
    dt = dl.find('dt')
    if dt:
        # 日付の抽出
        date_text = dt.get_text(strip=True).split('92;n')[0].replace(" ","").replace("全体目次はこちら","").replace("※インターネット官報","").strip()

        dd = dl.find('dd')
        if dd:
            for li in dd.find_all('li', class_='articleBox'):
                title_tag = li.find('a', class_='articleTop')
                pdf_link = li.find('a', class_='pdfDlb')

                if title_tag and pdf_link:
                    title = title_tag.decode_contents().replace("<br/>", "").strip()
                    url = pdf_link['href']
                    results.append({
                        '日付': date_text,
                        'title': title,
                        'url': url
                    })

# 結果の表示
for r in results:
    date = r['日付']
    title = r['title'] 
    url = r['url'] 

    # pdfファイルURL作成
    url_parts = url.rsplit("/", 1)
    url_base = url_parts[0]
    filename = url_parts[1].replace("f.html", ".pdf")
    converted_url = f"{url_base}/pdf/{filename}"
    
    # pdfURLファイル名を作成
    full_url = urljoin(base_url, converted_url)
    base_filename = date + "_" + title + "_" + filename.replace("f.html", ".pdf")

    # ダウンロードして保存
    print(f'Downloading {full_url} ...')
    try:
        response = requests.get(full_url)
        response.raise_for_status()
        with open(os.path.join(download_dir, base_filename), 'wb') as f:
            f.write(response.content)
        print(f'Saved: {base_filename}')
        time.sleep(10)
    except Exception as e:
        print(f'Failed to download {full_url}: {e}') 

2025-03-23

はてな匿名ダイアリーを非公開でブクマするbot作成方法

はてなブックマークAPIを利用して 非公開 でブックマークする方法をご説明します。

1. はてなAPI認証情報を取得

1. はてな開発者向けページ で APIキーを取得。

2. OAuthトークンを発行する。(個人用のスクリプトなら「パーソナルアクセストークン」推奨)

2. APIリクエストパラメータ

ブックマークを 非公開 にするには、APIリクエストボディに private フィールドを 1 に設定します。

エンドポイント:

POST https://bookmark.hatenaapis.com/rest/1/my/bookmark


リクエストボディ(JSON): 
{
    "url": "https://anond.hatelabo.jp/xxxxxxx",
    "comment": "自動ブックマーク",
    "private": 1
}

これで 非公開のブックマーク になります

3. Pythonスクリプト

以下のコードを実行すれば、10分以内の匿名ダイアリー記事ランダムに 非公開 でブックマークできます


import feedparser
import requests
import random
import time
from datetime import datetime, timezone, timedelta

# はてなAPI認証情報
HATENA_API_KEY = "あなたAPIキー"
HATENA_USERNAME = "あなたはてなID"

# はてな匿名ダイアリーRSS URL
RSS_URL = "https://anond.hatelabo.jp/rss"

# 10分以内の投稿を取得
def get_recent_entries():
    feed = feedparser.parse(RSS_URL)
    recent_entries = []
    now = datetime.now(timezone.utc)
    for entry in feed.entries:
        published_time = datetime(*entry.published_parsed[:6], tzinfo=timezone.utc)
        if (now - published_time) < timedelta(minutes=10):
            recent_entries.append(entry.link)
    return recent_entries

# はてなブックマークに非公開で追加
def bookmark_entry(entry_url):
    url = "https://bookmark.hatenaapis.com/rest/1/my/bookmark"
    headers = {
        "Authorization": f"Bearer {HATENA_API_KEY}",
        "Content-Type": "application/json"
    }
    payload = {
        "url": entry_url,
        "comment": "自動ブックマーク",
        "private": 1  # 非公開設定
    }
    response = requests.post(url, json=payload, headers=headers)
    return response.status_code

# メイン処理
while True:
    entries = get_recent_entries()
    if entries:
        entry = random.choice(entries)
        status = bookmark_entry(entry)
        print(f"非公開ブックマーク: {entry}, ステータス: {status}")
    time.sleep(600)  # 10分ごとに実行

4. 注意点

• 実行環境: Python 3.x が必要。requests と feedparser をインストール (pip install requests feedparser)

• 実行間隔: time.sleep(600) で10分ごとに実行

API制限: はてなAPIにはリクエスト制限があるため、短時間で大量に実行しないように注意

OAuth認証: APIキーだけでなく、OAuthトークンを使うほうがより安全

このスクリプトを実行すれば、最新の匿名ダイアリー投稿10分以内のものからランダムに選び、非公開でブックマークする ことができます

はてな匿名ダイアリーbotブクマするのは運営も認めてる行為なので、みんなでbotを使ってブクマしよう!

2025-03-06

コマンド区切り文字TABだったら世界平和だった

OSコマンドプロンプトはどうしてスペースを区切り文字としたのだろうか?
もしタブを区切り文字として採用していれば、混乱はもっと少なかったと思うのだけど。

と、いう素朴な疑問からスタートし、タブが唯一の由緒正しい区切り文字として採用されていた世界線考察しました。

https://grok.com/share/bGVnYWN5_aa43b346-0185-4f3e-ac4f-7712a59545c8

この世界線で、TABのみが由緒正しく使える使える区切り文字です。

そうなると、ファイル名の半角スペース問題もなくなる。

tsvデータが主流となりcsvデータparseで悩む人にいなくなる。

スペースとタブを区別するために[TAB]のようなフォントも作られるだろう。

さらに、プログラムのインデントもスペースではなく、すべてタブで行われるようになり、タブ幅は2なのか4なのか8なのか論争も起きなかっただろう。個人エディタの設定で変えればいいだけですしね。

世界平和になったはずです。

もちろん、ASCII規格のUS(Unit Separator, ASCII 31):ユニット(フィールド)の区切り、に相当するキーキーボードに搭載されていたとしたら、それを使うのが最良だったと思う。

しかしながら、多くのキーボードにはスペースキーかタブキーぐらいしかなく、これを使うことはできなかったでしょうね。

まとめ

コマンド区切り文字にスペースを使ってしまった、インデントにスペースを使ってしまった。

この辺りの失敗が多くの人を悩ませていると思う。

もしコンピュータ歴史をやり直せるのであれば、この時にタブを採用していればねえ。

2024-05-24

anond:20240523100428

ダブスタ検証用のスクリプト簡単に書いたよ(AIで)

import requests
import json
from urllib.parse import quote

def fetch_bookmarks(url):
    try:
        # URLエスケープ
        escaped_url = quote(url, safe="")
        api_url = f"https://b.hatena.ne.jp/entry/json/?url={escaped_url}"

        response = requests.get(api_url)
        response.raise_for_status()

        try:
            return response.json()
        except json.decoder.JSONDecodeError as e:
            print(f"Error decoding JSON from {api_url}: {e}")
            print("Response content:", response.text)
            return []
    except requests.exceptions.RequestException as e:
        print(f"Error fetching bookmarks from {api_url}: {e}")
        return []

def find_common_bookmarks(bookmarks1, bookmarks2, url1, url2):
    common_users = set(bm["user"] for bm in bookmarks1 if bm["comment"]) & set(bm["user"] for bm in bookmarks2 if bm["comment"])
    common_bookmarks = []
    for user in common_users:
        comments = []
        for bm in bookmarks1:
            if bm["user"] == user and bm["comment"]:
                comments.append({"url": url1, "comment": bm["comment"], "timestamp": bm["timestamp"]})
                break
        for bm in bookmarks2:
            if bm["user"] == user and bm["comment"]:
                comments.append({"url": url2, "comment": bm["comment"], "timestamp": bm["timestamp"]})
                break
        if len(comments) == 2:
            common_bookmarks.append({"user": user, "comments": comments})
    return common_bookmarks

if __name__ == "__main__":
    url1 = "https://news.yahoo.co.jp/articles/f9966c4ccc374fc88babbb50175a9ea844c99638"
    url2 = "https://www.asahi.com/articles/ASN6K7F64N6KUJHB00L.html"

    data1 = fetch_bookmarks(url1)
    data2 = fetch_bookmarks(url2)

    common_bookmarks = find_common_bookmarks(data1["bookmarks"], data2["bookmarks"], url1, url2)

    print(json.dumps(common_bookmarks, indent=2, ensure_ascii=False))

url1, url2のところを対象としたいものに変えれば使えるよ

抽出対象となるのは以下のユーザーだよ

バグあったら直して使ってね

※てかはてな匿名ってシンタックスハイライト記法使えないんだね、使って表示確認したら500エラーになったわ

2023-12-14

架空言語、という事にしておいてほしいんだが

    List<Record> rows = DBから持ってくる();

    // 合計金額を求める
    String total = "0";
    for (Record r : rows) {
        BigDecimal temp = BigDecimal.parse( カンマを削除する関数(total) ); 
        BigDecimal temp2 = temp + r.金額;
        total = カン区切り文字列にする関数(temp2); 
    }

    // やったー合計金額計算してカン区切り文字列にできたよー
    return total;

とか

    // async await は非同期処理を同期してくれる魔法言葉だって!よく知らんけど
    await axios.get('/foo')
        .then(function(result) {
             // やったー結果が得られたよー
        });

とか

正直なところ、こういうコードのお守りするの、そろそろキツい。。。

2023-08-14

anond:20230813211225

たとえは、0 / float.Parse("0") とかすると、Infinity になるんじゃね?まずいと思うが。

2023-08-13

anond:20230813185410

なんかおかしいか

信用できない文字列に対してこそ、よくテストされ尽くしたfloat.Parse()を使うのは当然だと思うんだが。。

2023-07-18

気象庁バグがずっと放置されてる

この時期になるとずっと気象庁の気温ランキングを眺めて「今日暑いなー」って見てるんだが

https://www.data.jma.go.jp/obd/stats/data/mdrr/rank_daily/data00.html

このページの「観測値」が昔からずっとバグっててめっちゃ気になってる

「35.9 ]」っていう感じで、「]」が入ってる

もうバグの原因はほぼほぼ目に見えてて、ここはJSONで「 [ 34.0, 34.5, 35.0, 35.9 ]」っていう値が入ってて

それをJSON.parseするんじゃなくてsplit()とか使ってしかもlength-1とかで最新値を取ってる

なので後ろの括弧がそのまま入ってしまってる

こんなのめちゃくちゃわかりやすバグだし、多分気象庁側も把握してるんだろうけど

多分発注しないとダメとかテスト必要だとかでずっと放置されてる

めっちゃ気になるのにずーーーーーっとこのままなのが凄くダサい

何がダサいってこういうのをアジャイルに直すことができない日本ソフトウェアに対する態度がすげーダサい

2023-04-19

strftime

strptime

がどっちが文字を日付にするやつか

日付を文字にするやつか

もう何年も忘れ続けて

その都度検索してたんだけど

わかった!!!

Format のfとParseのpだわ!!!!!!!

これで検索しなくても覚えられる😁

2022-08-18

俺はみずほと同じぐらいのコードしか書けないのに気が付いた

話題ホットエントリ問題、解いてみた。

結論タイトルの通り

不正解score += 0; と書いているのは静的解析がelse節を省略すると指摘してくるから

何もしていない、はそのとおり。

不等号がいい具合に化けてるのでそのままにしておく。


import java.util.*;


public class Main {
    public static void main(String[] args) {
        // 入力parse
       (中略:int numに問題の数、String list[i][] に問題リストを格納している)

        // 採点
        int point = 0;
        for (int i = 0; i < num; i++) {
            String question = list[i][0];
            String answer   = list[i][1];
            
            if (question.equals(answer)) {
                // 完全一point += 2;
            } else {
                if (question.length() == answer.length()) {
                    // 文字数は等しい(部分点の可能性がある)
                    point += scoring(question, answer);                    
                } else {
                    // 不正解
                    point += 0;
                }                
            }
        }
        System.out.println(point);
    }

    // 長さが同じ文字列を採点する
    // 長さが違う文字列を受け渡したときは正しく動作しない
    private static int scoring(String question, String answer) {
        int length = question.length();
        int score  = 2;
        for (int i = 0; i < length; i++) {
          (中略:文字が違うたびにscoreを-1して、socreが0以下になったらそのままreturn)
        }
        return score;
    }
}

2021-01-10

このような文章は、MTLはおろか、ほとんどの人には解析できないでしょう。

A sentence like this would be impossible for most people to parse, let alone MTL.

DeepLすげーな、最後無視してるしニュアンスは全部落ちてるけど

2020-11-28

プログラミング勉強、詳細の疑問大事だけどあとでもいいじゃん?

C#入門書を読んでいて、第4章くらいに4ページくらい割いて書いてあるキャスト

( )で変換するのと int.Parse() で変換するのとどう違うのか(どちらか片方でいいのではないか)をネットで調べ、

なんか as とか int.TryParse() とか出てきてあーんー参照型と値型がーになってダウンキャストとポリフォーニズムでお昼ごはん夜ご飯になったところで切り上げた

オーケー、とりあえず現状詳細わからんでいいわ

型の違う数値同士のときが ( ) で、文字列から数値に変換したいときが int.Parse() とかだな

入門書に書いてあるそのまんまだな

プロの取捨選択最高

もういいや次ページ行こう全然進まん

2020-05-09

anond:20200509091500

では横ですが、ワテクシの特技のRを少々……

eval(parse(text=paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(paste0(

2019-04-02

いま、この「自分の全記事を一括削除するスクリプト」は動かない?

anond:20130927152907

いま、この「はてな匿名ダイアリー自分の全記事を一括削除するスクリプト」は、動かない?

httphttps、URL などは、適切に直した。

・POSTに与える最後csrf を取ってくる parse が間違って、 rkm= が空になっている。正規表現parse していたのを直して、 o+XXXXXXXXXXXXXXX/g を拾ってくるようにした。

・POSTに与える delete=が元々の %8d%ed%8f%9c%82%b7%82%e9 でも削除できないし、「 %e5%89%8a%e9%99%a4%e3%81%99%e3%82%8b 」でも削除できなかったし、これってマジックナンバーなん?

2019-01-01

Graphviz を使って Python抽象構文木を生成する。



1. こんな感じで使います


1.1. スクリプトとして使用する。

$ python parser.py sample.py


1.2. モジュールとして使用する。

import parser
code = '''
a  = 1 + 1
print(a)
'''
graph = parser.create_graph(code)
graph.render("sample")


2. ソースコード parser.py はこんな感じです。

import ast
import sys
import graphviz


def create_graph(lines):
    graph = graphviz.Graph(format='png')
    root = ast.parse(lines)
    node_list = [root]
    _setup(graph, node_list)
    return graph


def _setup(graph, node_list):
    # node
    node = node_list[-1]
    node_identity = str(len(node_list))
    node_name = type(node).__name__
    graph.node(node_identity, node_name)

    # children
    for child in ast.iter_child_nodes(node):
        node_list.append(child)
        child_identity = str(len(node_list))
        graph.edge(node_identity, child_identity)
        _setup(graph, node_list)


if __name__ == '__main__':
    file_name = sys.argv[1]
    with open(file_name) as file:
        lines = file.read()
    graph = create_graph(lines)
    graph.render(file_name)

2018-08-06

Facebook は買ったサービスを衰退させる)

FacebookVR の雄 Oculus も買っています。今は VR といえば Oculus ですが、VR にはどんどん大きな会社が参入してきています近いうちに Oculus の影響力は下がっていくことでしょう。それを見た Facebook経営層は、Parse と同じ判断をするはずです。その時、Oculus 用のソフトを開発していた人たちは、今の Parse で開発していた人たちと同じ辛さを経験するでしょう。これを予見できる開発者たちは、早期に Oculus から撤退するはずです。そうすると VR に対する Oculus の影響力は加速度的に小さくなります

まり、何が言いたいかというと、今回の判断開発者Facebook 離れを引き起こす判断であり、Facebook に買われたサービスは衰退することを宿命づけてしまった大きな事件の一つだったということです。

もう Facebook は有望な会社は買わないでくれ!と本気で思う一件でした。

2018-03-17

anond:20180316232605

Excelを持っているならはてブJSONデータをそのまま取り込めるそうだからそのデータを使ってブクマが付いた時間グラフが描けそう。

例えばこんな感じでJSONデータが取れる。 http://b.hatena.ne.jp/entry/jsonlite/https://anond.hatelabo.jp/20180315232737

Excel持ってないならスクリプトCSVにしてしまえばいい。

rubyスクリプトだとこんな感じ。(Mechanize無し版に差し替え。なぜMechanizeを使っていたかと言うとはてブUser-Agentが空だと値を返してくれないから。ちょっと長くなるが自前でUAを渡すようにした。)

#!/usr/bin/ruby

require 'uri'

require 'net/http'

require 'json'

require 'csv'

site = ARGV[0]

json_uri = URI.parse("http://b.hatena.ne.jp/entry/jsonlite/%s" % [site])

response = Net::HTTP.start(json_uri.host, json_uri.port) do |http|

http.get(json_uri.path, "User-Agent" => "Mozilla/5.0")

end

json_data = JSON.parse(response.body)

json_data['bookmarks'].each do |bookmark|

puts [bookmark['user'], bookmark['timestamp'], bookmark['comment'], bookmark['tags'].to_s].to_csv

end

引数に取得したいページのURLを入れる。hatebuapi-csv.rbという名前で保存したとしたらこんな感じで実行。

% hatebuapi-csv.rb https://anond.hatelabo.jp/20180315######## > 結果.csv

このケースでは朝の7時から爆発的にブクマが付き始める様子が分かる。

https://imgur.com/66FlJIB

2015-11-29

aliexpressをrubyスクレイピング

rubyスクレイピング

aliexpressの検索結果から

検索結果のURLを抜き取るのは、

結構簡単にできた。

ここから、ページ切り替えてURL収集する処理も追加すれば、

クローロング部分は完成。

あとは、各ページに対するスクレイピング問題



require 'open-uri'

require 'nokogiri'

# スクレイピング先のURL

url = 'http://ja.aliexpress.com/category/200003482/dresses.html?spm=2114.52010108.6.7.gT0qlW&addpid=32546825642&isOnSale=yes%22'

charset = nil

html = open(url) do |f|

charset = f.charset # 文字種別を取得

f.read # htmlを読み込んで変数htmlに渡す

end

# htmlパース(解析)してオブジェクト作成

doc = Nokogiri::HTML.parse(html, nil, charset)

num=0

doc.css('a[class = "product "]').each do |product|

p product.attribute("href").text

p num = num+1

end

2014-12-24

イブクリスマスも予定のない俺とみんなのためのエロサイトを作った

作ったサイト

エロ動画を色々なところから収集するサイトです。

skrsvideo

http://skrsv.info/

サーバー選び

今回プログラミング言語Rubyを選択したため、基本的にはVPSクラウド的なサーバーLinuxが動作する環境を探しました。

エロサイト運営するにあたって問題になるのがサーバー選びです。

基本的日本レンタルサーバーではアダルトサイト運用を禁止しています

普段使っているさくらVPSが利用できず、AWSもなんだかグレーな感じ(東京リージョン以外なら・・・?)

そんなわけで探し、GMOグループWebkeepersを使いました。

Webkeepersのサーバー海外にあるらしいです。

質問アダルトサイト運用はできますか?

http://faq.webk.net/faq/index.php?qc=1&qc_sub=4&id=99

使っても良いよ〜というお墨付き

そして価格も手頃だったためここに決定。

システム

DBMariaDB
WebサーバNginx
フレームワークRuby on Rails

MariaDBを選んだ理由はなんとなく、MySQLとの違いはほぼありません。利用するGEMmysql2でいけます

NginxWebサーバで、ページキャッシュもしています

ちなみにJavaScriptは使わずすべてCSSで作る方針しました。

スマホPC対応のためにMedia Queryでレスポンシブにしています

Webの流れ

Nginx

unicorn

Ruby on Rails

MariaDB

という流れです

使っているGEM

gem 'mysql2'

gem 'rails_config'

gem 'kaminari'

gem 'haml-rails'

gem 'sass-rails'

gem 'nokogiri'

gem 'unicorn'

フロントhamlsassで、難しいことはしていないのでcompassはいれませんでした。

あとはデバッグ用にrails_config、pry系が入っています

クローリングスクレイピングでnokogiriを使います

クローラー

skrsvideoでは動画URLを取得するためにクローラーもどきスケジューリングして収集しています

スケジューリングにはcrontabを使用しています

crontabでRakeタスクを定期的に叩きます

コマンドはこんな感じ

RAILS_ENV=production bundle exec rake item:search


Rakeタスクはnokogiriでxvideosへのリンクを集めています

doc = Nokogiri::XML( open(URI.parse(url)).read )

urls = []

doc.css('a').each do |link|

 urls.push link[:href] if link[:href] =~ /xvideos.com\/video(\d+)/

end

Nokogiriのスクレイピングでaタグリンクを取得し、URLxvideosのものかチェックして保存って感じです。

動画を探し終えるとaタグからランダムピックアップし次のページに進んでいきます

動画が見つかったページはドメインDBに記録して、しばらくしたら再びクローリングをするようにし、収集効率化。

サイト機能

(45 min)←コレの安心感は異常wwwwww

http://blog.livedoor.jp/dennououjo/archives/39873075.html

これを思い出して、動画時間を表示するようにしました。

30分以上の表示はちょっと頑張ったところ

http://skrsv.info/30_min_more


AV女優タグが表示されるようになっています

これはタイトル文字列から部分一致で引っかかったものを表示しています

AV女優名前を表示するためにWikipediaからとってきたら、ちょっと膨大な数になってしまったため断念。

どんだけ女優いるんだって感じですね。

DMMランキングに載っていた方だけをとりあえず入れています

タグも同様にDMMから

おわりに

1日でつくろうと思っていたら思った以上にサーバーが見つからないで、サーバー選びに1日かかってしまいました。

あとはFC2とか対応できたらいいなーと思います

2014-07-11

System.Net.Sockets.UdpClient でブロードキャスト送信

C# で書くと

var ep = new System.Net.IPEndPoint(System.Net.IPAddress.Parse("255.255.255.255"), 2048);

var soc = new System.Net.Sockets.UdpClient(ep);

とすれば

型 'System.Net.Sockets.SocketException' のハンドルされていない例外が System.dll で発生しました

追加情報:要求したアドレスコンテキスト無効です。

例外が発生する。

ここは

var u = new System.Net.Sockets.UdpClient();

と空のUDPソケットを作成しておいて、Send メソッドアドレスを指定する方法をとる。

ちなみに u.EnableBroadcast には false を代入しても、私の環境ではなぜだかうまく送信された。

(本文に関係ないけど、はてな記法コード(cs)の書式指定すると、プロキシエラーになる。。)

 
ログイン ユーザー登録
ようこそ ゲスト さん