1

Тема: AHK: Пример скачивания записи ЖЖ с развёрнутыми комментариями

Скрипт скачивает запись ЖЖ с подстраницами комментариев (не переходя по трэдам — свёрнутым комментариям) и извлекает запись и информацию о смещении каждого комментария —

https://miss-tramell.livejournal.com/1564043.html?page=%pg_num%&style=mine

* [Поскольку от журнала к журналу стили имеют разный код, скачивание должно происходить в стиле одного журнала (style=mine), через который скрипт логинится в ЖЖ. При этом комментарии в его стиле должны иметь не JS, а HTML код].

Затем загружаются комментарии в JSON-виде (доступные, даже когда владелец журнала закрыл их отображение у себя) —

https://www.livejournal.com/__rpc_get_thread?journal=miss-tramell&itemid=1564043&flat=&skip=&media=&expand_all=1&page=%pg_num%

парсятся и объединяются с записью и инфой о смещениях комментов, в итоге получая развёрнутые кооментарии сплошным текстом.

#MaxMem 500
SetBatchLines -1
#SingleInstance Force


;----------------------------------------------------------------------
proxy := "109.207.62.69:8080"
ProxyOrNot:=0 ; задействовать прокси, или нет
LoginOrNot:=1 ; логиниться, или нет
ComObjError(false)
HTTP := ComObjCreate("WinHTTP.WinHTTPRequest.5.1")
login    := "**********"
password := "**********"
path = %A_ScriptDir%\
SplashBC = 00B0EA
;----------------------------------------------------------------------


loop_link = https://miss-tramell.livejournal.com/1564043.html

RegExMatch(loop_link, "s)(https?://(.*?)\.livejournal\.com/)?(\d+)\.html(\?style=mine)?", match)
lj_uname := match2
lj_pgnum := match3

dest_path = %path%%lj_pgnum%.html
if (NOT FileExist(dest_path))
   Gosub, dwn
else
{
   msgbox Файл существует`n%dest_path%
   ExitApp
}
dwn:

If not (LoginMode == 1)  ;при первом запуске один раз:
{
   If LoginOrNot   ;если надо логиниться
      GoSub, login
   LoginMode = 1
}

; скачивание записи с подстраницами комментариев
n := 1, LJ_Tmp_Pages :="", cmtCount :=""
;HTTP := ComObjCreate("WinHTTP.WinHTTPRequest.5.1")
loop
{
   HTTP.Open("GET", "https://" lj_uname ".livejournal.com/" lj_pgnum ".html?page=" n "&style=mine", true)
   If ProxyOrNot
      HTTP.SetProxy(2, proxy)
   GoSub, SetRequestHeader
   HTTP.Send()
   HTTP.WaitForResponse()
   ResponseText := HTTP.ResponseText

   LJ_Tmp_Pages .= "`n" ResponseText

   If A_Index = 1
   {
      cmtCount := RegExReplace(ResponseText, "s)^.*<a [^>]*\.html\?page=(\d+)(&style=mine)?(#comments)?'>(<b>)?(\[)?\1(\])?(</b>)?</a>.*?$", "$1")
      LjPost := RegExReplace(ResponseText, "s).*?(<div id=""content"">.*?)<div class=""quickreply"" id=""ljqrtentrycomment"".*", "$1")
      LjPost := RegExReplace(LjPost, "s)^(.*<div class=""asset-tags"">.*?</div>).*", "$1</div>`n</div>`n</div>`n</div>`n</div>`n</div>`n</div>`n</div>`n</div>`n")
      PostTitle := RegExReplace(ResponseText, "s).*?<title>(.*?)</title>.*", "$1")
      PostTitle := RegExReplace(PostTitle, "\R+\s*|\s+$", "")
      If NOT InStr(ResponseText, "id=""ljcmt") or NOT ResponseText~="\.html\?page=\d+&style=mine?(#comments)?'><b>\[\d+\]</b>"
         break
   }

   if (n >= cmtCount)
      break
   n+=1 ; смещение по offset
}

ResponseText =
CmtThreads =

loop
{
   ;скачивание страниц комментов в JSON формате
   get_thread := "https://www.livejournal.com/__rpc_get_thread?journal=" lj_uname "&itemid=" lj_pgnum "&flat=&skip=&media=&expand_all=1&page=" A_Index
   HTTP.Open("GET", get_thread, true)
   GoSub, SetRequestHeader
   HTTP.Send()
   HTTP.WaitForResponse()
   ResponseText := HTTP.ResponseText

   StrReplace(ResponseText, "{""shown"":",, CmtCountFromPage)

   If A_Index = 1
      cmtCountAll := RegExReplace(ResponseText, "s)^\{""replycount"":(\d+).*$", "$1")

   If NOT InStr(ResponseText, "{""replycount"":") or (CmtCountFromPage = 0)
      break

   Gosub, JSON_Parse

   CountFromPage += CmtCountFromPage

   ResponseText =
   If (CountFromPage > cmtCountAll)
      break
}

CmtThreads := RegExReplace(CmtThreads, "s)<a [^>]*><img [^>]*></a>\n<div [^>]*><img [^>]*>&nbsp.<a title="""" href=""""></a> – <a [^>]*></a>&nbsp.</div>\n(<div [^>]*><b>\(Удалённый комментарий\)</b></div>)", "$1")

match := "", Pos := 1
While ( Pos := RegExMatch(LJ_Tmp_Pages, "s)(<div\s+id=""ljcmt)(\d+)("" style=""margin-left: ?\d+px;?"")", match, Pos + StrLen(match)) )
    StringReplace, CmtThreads, CmtThreads, <div id="ljcmt%match2%", %match1%%match2%%match3%, All

   GoSub, Sklonenie
   Gosub, htmlcode


fileappend, %htmlcode%`n`n%LjPost%`n`n`n, %dest_path%, UTF-8
fileappend, <center><h2>%CountFromPage%</h2></center>`n`n`n%CmtThreads%`n`n</body>`n</html>`n`n, %dest_path%, UTF-8

CountFromPage =
cmtCountAll =
CmtCountFromPage =
LjPost =
PostTitle =
cmtCount =
lj_pgnum =
lj_uname =
LJ_Tmp_Pages =
CmtThreads =
ResponseText =
LJ_Tmp_Pages =
match =
match1 =
match2 =
match3 =
match4 =
loop_link =
str =
unit =

return

Sklonenie:
   unit := CountFromPage - (CountFromPage // 100 * 100)
   unit > 20 ? dPart := unit // 10 * 10 : dPart := 0
   str := (unit == 1 + dPart) ? "комментарий" : (unit > 1 + dPart && unit < 5 + dPart) ? "комментария" : "комментариев"
   CountFromPage := CountFromPage " " str
return

JSON_Parse:
obj := JSON.Parse(ResponseText)
keys =
(
userpic
uname
commenter_journal_base
dtalkid
upictitle
article
ctime
deleted
parent
subject
striked
userhead_url
dname
)

for k, v in obj.comments  {
   Loop, parse, keys, `n, `r
      %A_LoopField%%k% := SearchKey(v, A_LoopField)

   if (userpic%a_index% = "")
      userpic%a_index% := "https://l-stat.livejournal.net/img/userpics/userpic-user.png?v=15821"
   if (striked%a_index% = "1")
      dname%a_index% := "<s>" dname%a_index% "</s>"
   if (deleted%a_index% = "1")
   {
      userpic%a_index% =
      article%a_index% := "<b>(Удалённый комментарий)</b>"
   }
   ctime%a_index% := RegExReplace(ctime%a_index%, "s) UTC", "")

   CmtThreads .= "`n<div id=""ljcmt" dtalkid%a_index% """ class=""comment""><a name=""" dtalkid%a_index% """></a>`n<a href=""#" parent%a_index% """><img class=""img"" title=""" upictitle%a_index% """ src=""" userpic%a_index% """></a>`n<div class=""info""><img class=""userhead_url"" src=""" userhead_url%a_index% """>&nbsp;<a title=""" uname%a_index% """ href=""" commenter_journal_base%a_index% """>" dname%a_index% "</a> – <a href=""https://" lj_uname ".livejournal.com/" lj_pgnum ".html?thread=" dtalkid%a_index% "#t" dtalkid%a_index% """>" ctime%a_index% "</a>&nbsp;" subject%a_index% "</div>`n<div class=""content"">" article%a_index% "</div>`n</div>`n"
}
return

login:
SplashImage,, y-2 x60 w40 h14 M C11 ZH0 ZW0 ZX1 ZY1 B1 CTffffff CW%SplashBC% FM8 FS6 WM600 WS400,, login
;--------------------------------------------
;Авторизация:
;--------------------------------------------
HTTP.Open("GET", "https://www.livejournal.com/login.bml", true)
If ProxyOrNot
   HTTP.SetProxy(2, proxy)
GoSub, SetRequestHeader
HTTP.Send()
HTTP.WaitForResponse()
RegexMatch(HTTP.ResponseText, "s)name=""lj_form_auth"" value=""([^""]*)""", match)
PostData := "lj_form_auth=" match1 "&ref=&returnto=%2F&user=" login "&password=" password "&remember_me=1&action%3Alogin="
StringReplace, PostData, PostData, :, `%3A, All

HTTP.Open("POST", "https://www.livejournal.com/login.bml", true)
If ProxyOrNot
   HTTP.SetProxy(2, proxy)
HTTP.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded")
GoSub, SetRequestHeader
HTTP.Send(PostData)
HTTP.WaitForResponse()
If !Instr(HTTP.ResponseText, "-->Выйти<!--")
;   msgbox Log On!
;Else
   msgbox, LogOn Failed!
ResponseText =
;--------------------------------------------
SplashImage, OFF
Return

SetRequestHeader:
HTTP.SetRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko)")
HTTP.SetRequestHeader("Pragma", "no-cache")
HTTP.SetRequestHeader("Cache-Control", "no-cache, no-store")
HTTP.SetRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT")
return

htmlcode:
htmlcode=
(
<!DOCTYPE html>
<html>
<head>
<title>%PostTitle%</title>
<meta http-equiv=Content-Type content="text/html; charset=UTF-8">
<style type="text/css"> <!--
body {
  Xwidth: 100`%;
  margin-bottom: 10`% !important;
}
.comment {
  Xbackground-color:#EEEEEE;
  padding: 20px 20px 0px 20px;
}
.content {
  width: 95`%;
  padding: 10px 20px 20px 60px;
}
.comment .img {
  float: left;
  width: 60px;
  Xheight: 60px;
  margin: 3px 10px 5px 0px;
}
.info {
  color:#767676;
  font-size:90`%;
}
.info a {
  font-weight:bold;
}
--></style>

<link rel="stylesheet" type="text/css" href="https://l-stat.livejournal.net/??voxhtml/base.css,voxhtml/default/screen.css,voxhtml/woodcut/screen.css,voxhtml/ljextras.css,voxhtml/widget-threeposts.css,voxhtml/widget-ramblerpartner.css?v=1530780559" media="all">
</head>
<body>
`n`n`n`n
)
Return

SearchKey(obj, key)  {
   for k, v in obj  {
      if (k = key)
         Return v
      
      if IsObject(v)  {
         res := SearchKey(v, key)
         if (res != "")
            Return res
      }
   }
}

class JSON
{
   static JS := JSON._GetJScripObject()
   
   Parse(JsonString)  {
      try oJSON := this.JS.("(" JsonString ")")
      catch  {
         MsgBox, Wrong JsonString!
         Return
      }
      Return this._CreateObject(oJSON)
   }

   _GetJScripObject()  {
      VarSetCapacity(tmpFile, (MAX_PATH := 260) << !!A_IsUnicode, 0)
      DllCall("GetTempFileName", Str, A_Temp, Str, "AHK", UInt, 0, Str, tmpFile)
      
      FileAppend,
      (
      <component>
      <public><method name='eval'/></public>
      <script language='JScript'></script>
      </component>
      ), % tmpFile
      
      JS := ObjBindMethod( ComObjGet("script:" . tmpFile), "eval" )
      FileDelete, % tmpFile
      JSON._AddMethods(JS)
      Return JS
   }

   _AddMethods(ByRef JS)  {
      JScript =
      (
         Object.prototype.GetKeys = function () {
            var keys = []
            for (var k in this)
               if (this.hasOwnProperty(k))
                  keys.push(k)
            return keys
         }
         Object.prototype.IsArray = function () {
            var toStandardString = {}.toString
            return toStandardString.call(this) == '[object Array]'
         }
      )
      JS.("delete ActiveXObject; delete GetObject;")
      JS.(JScript)
   }

   _CreateObject(ObjJS)  {
      res := ObjJS.IsArray()
      if (res = "")
         Return ObjJS
      
      else if (res = -1)  {
         obj := []
         Loop % ObjJS.length
            obj[A_Index] := this._CreateObject(ObjJS[A_Index - 1])
      }
      else if (res = 0)  {
         obj := {}
         keys := ObjJS.GetKeys()
         Loop % keys.length
            k := keys[A_Index - 1], obj[k] := this._CreateObject(ObjJS[k])
      }
      Return obj
   }
}

2 (изменено: DD, 2018-07-10 21:55:47)

Re: AHK: Пример скачивания записи ЖЖ с развёрнутыми комментариями

Еще можно качать запись в виде мобильной версии, для которой стили не меняются:

https://m.livejournal.com/read/user/miss-tramell/1564043