# zjmf-api-doc crawler for http://w2.test.idcsmart.com/doc # Usage: powershell -ExecutionPolicy Bypass -File fetch_api_docs.ps1 # Outputs (same dir): # doc_list.json raw api catalog (JSON) # raw/*.html per-action detail pages (resumable; existing files are skipped) # zjmf-api.md merged Markdown doc # # NOTE: this script is ASCII-only on purpose (avoids PS5.1 script-encoding quirks). # The doc content itself is Chinese because it comes from the crawled pages. param( [string]$BaseUrl = 'http://w2.test.idcsmart.com', [int]$SleepMs = 80, [int]$MaxRetries = 3 ) $ErrorActionPreference = 'Continue' $OutDir = Split-Path -Parent $MyInvocation.MyCommand.Path $RawDir = Join-Path $OutDir 'raw' $ListPath = Join-Path $OutDir 'doc_list.json' $MdPath = Join-Path $OutDir 'zjmf-api.md' New-Item -ItemType Directory -Force -Path $RawDir | Out-Null function Invoke-WithRetry([string]$url) { for ($i = 0; $i -lt $MaxRetries; $i++) { try { return Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 30 } catch { Start-Sleep -Milliseconds 800 } } Write-Warning "fetch failed: $url" return $null } # ---------- 1. catalog ---------- if (-not (Test-Path $ListPath)) { Write-Host 'Fetching /doc/list ...' $r = Invoke-WithRetry "$BaseUrl/doc/list" if ($r) { [System.IO.File]::WriteAllText($ListPath, $r.Content, [System.Text.Encoding]::UTF8) } } $tree = Get-Content $ListPath -Raw -Encoding UTF8 | ConvertFrom-Json # ---------- 2. flatten all actions (multi-level children) ---------- $actions = New-Object System.Collections.ArrayList function Add-Actions($nodes) { foreach ($n in $nodes) { if ($n.actions) { foreach ($a in $n.actions) { if ($a.name) { [void]$actions.Add([pscustomobject]@{ name = $a.name title = $a.title url = $a.url method = $a.method desc = $a.desc params = $a.param rets = $a.return }) } } } if ($n.children) { Add-Actions $n.children } } } Add-Actions $tree.list $total = $actions.Count Write-Host "Total actions: $total" # ---------- 3. fetch detail pages (resumable) ---------- function Get-SafeName([string]$name) { return ($name -replace '[^A-Za-z0-9._-]', '_') } $done = 0 foreach ($a in $actions) { $rawFile = Join-Path $RawDir ((Get-SafeName $a.name) + '.html') if (Test-Path $rawFile) { $done++; continue } $r = Invoke-WithRetry ("$BaseUrl/doc/info?name=" + $a.name) if ($r) { [System.IO.File]::WriteAllText($rawFile, $r.Content, [System.Text.Encoding]::UTF8) } $done++ if (($done % 25) -eq 0) { Write-Host ("progress {0}/{1}" -f $done, $total) } Start-Sleep -Milliseconds $SleepMs } Write-Host "Fetch done: $done/$total" # ---------- 4. parse one detail page ---------- function Parse-DocHtml([string]$html) { $doc = [pscustomobject]@{ title = ''; url = ''; method = ''; desc = ''; json = ''; params = @() } if (-not $html) { return $doc } if ($html -match '

[^<]*([^<]*)

') { $doc.title = $Matches[1].Trim() } if ($html -match 'class="text-primary">([^<]*)

') { $doc.desc = $Matches[1].Trim() } if ($html -match '(.*?)') { $doc.json = ([System.Net.WebUtility]::HtmlDecode($Matches[1]) -replace '', "`n").Trim() } # url + method: line like:

[address] POST

$pMatch = [regex]::Match($html, '

[^<]*?\s+([^<]+?)\s*]*>\s*([^<]*?)\s*

', 'Singleline') if ($pMatch.Success) { $doc.url = $pMatch.Groups[1].Value.Trim() $doc.method = $pMatch.Groups[2].Value.Trim() } # param table after

$tblMatch = [regex]::Match($html, '

[^<]*

\s*]*>(.*?)', 'Singleline') if ($tblMatch.Success) { foreach ($row in [regex]::Matches($tblMatch.Groups[1].Value, '(.*?)', 'Singleline')) { $cells = [regex]::Matches($row.Groups[1].Value, '(.*?)', 'Singleline') if ($cells.Count -ge 2) { $vals = @() foreach ($c in $cells) { $vals += [System.Net.WebUtility]::HtmlDecode($c.Groups[1].Value).Trim() } $doc.params += , $vals } } } return $doc } function Get-ActionDetail($action) { $rawFile = Join-Path $RawDir ((Get-SafeName $action.name) + '.html') if (Test-Path $rawFile) { return Parse-DocHtml (Get-Content $rawFile -Raw -Encoding UTF8) } return $null } # ---------- 5. generate Markdown ---------- $sb = New-Object System.Text.StringBuilder [void]$sb.AppendLine('# zjmf finance API doc') [void]$sb.AppendLine('') [void]$sb.AppendLine(('> crawled from ' + $BaseUrl + '/doc at ' + (Get-Date -Format 'yyyy-MM-dd HH:mm') + ' | actions: ' + $total)) [void]$sb.AppendLine('> sources: doc_list.json + raw/*.html | re-crawl: powershell -ExecutionPolicy Bypass -File fetch_api_docs.ps1') [void]$sb.AppendLine('') function Write-ActionMd($a) { $d = Get-ActionDetail $a $title = '' $method = '' $url = '' $desc = '' if ($d) { $title = $d.title $method = $d.method $url = $d.url $desc = $d.desc } if (-not $title) { $title = $a.title } if (-not $method) { $method = $a.method } if (-not $url) { $url = $a.url } if (-not $desc) { $desc = $a.desc } $heading = '### ' + $title if ($method) { $heading += ' -- ' + $method.ToUpper() } if ($url) { $heading += ' ' + $url } [void]$sb.AppendLine($heading) [void]$sb.AppendLine('') [void]$sb.AppendLine('- controller: ``' + $a.name + '``') if ($desc) { [void]$sb.AppendLine('- desc: ' + $desc) } [void]$sb.AppendLine('') $paramRows = $null if ($d -and $d.params -and $d.params.Count -gt 0) { $paramRows = $d.params } elseif ($a.params -and $a.params.Count -gt 0) { $paramRows = $a.params } if ($paramRows -and $paramRows.Count -gt 0) { [void]$sb.AppendLine('**params**') [void]$sb.AppendLine('') [void]$sb.AppendLine('| name | type | required | default | other | desc |') [void]$sb.AppendLine('| --- | --- | --- | --- | --- | --- |') $isList = $paramRows[0] -is [System.Array] if ($isList) { foreach ($p in $paramRows) { $cells = @() foreach ($v in $p) { $cells += ('' + $v) -replace "`n", ' ' } $line = '| ' + ($cells -join ' | ') + ' |' [void]$sb.AppendLine($line) } } else { foreach ($p in $paramRows) { $f = @($p.name, $p.type, $p.require, $p.default, $p.other, $p.desc) [void]$sb.AppendLine('| ' + ($f -join ' | ') + ' |') } } [void]$sb.AppendLine('') } if ($d -and $d.json) { [void]$sb.AppendLine('**response example**') [void]$sb.AppendLine('') [void]$sb.AppendLine('```json') [void]$sb.AppendLine($d.json) [void]$sb.AppendLine('```') [void]$sb.AppendLine('') } if ($a.rets) { foreach ($r in @($a.rets)) { if ($r) { [void]$sb.AppendLine('- return-field: ' + $r) } } [void]$sb.AppendLine('') } } function Walk-ForMd($nodes) { foreach ($n in $nodes) { if ($n.actions -and $n.actions.Count -gt 0) { [void]$sb.AppendLine('---') [void]$sb.AppendLine('') [void]$sb.AppendLine('## ' + $n.title) [void]$sb.AppendLine('') foreach ($a in $n.actions) { if ($a.name) { Write-ActionMd $a } } [void]$sb.AppendLine('') } if ($n.children) { Walk-ForMd $n.children } } } Walk-ForMd $tree.list [System.IO.File]::WriteAllText($MdPath, $sb.ToString(), [System.Text.Encoding]::UTF8) Write-Host "Generated $MdPath" Write-Host 'All done'