0
点赞
收藏
分享

微信扫一扫

delphi 通过TNetHttpClient,获取小红书评论,解决x-s签名验证

一、小红书评论原理及步骤

1、评论接口

https://edith.xiaohongshu.com/api/sns/web/v2/comment/page?note_id=%s&cursor=%s

cursor起始为空。

2、展开更多回复评论接口

https://edith.xiaohongshu.com/api/sns/web/v2/comment/sub/page?note_id=%s&root_comment_id=%s&num=10&cursor=%s

3、请求数据头

  headers: {
    'Content-Type': 'application/json',
    'Accept':'application/json',
     'accept-language':"zh-CN,zh;q=0.9",
    'Referer': 'https://www.xiaohongshu.com',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36',
    'X-s': xs ,
    'X-t': xt,
    'Cookie':cookie,
  }

发送https get请求,X-s为签名验证;X-t为签名验证时间戳,必须有。需要帮助的加v:byc6352。

Cookie可以从浏览器复制出来。

二、获取流程及源代码

procedure TComment.working();
var
  bRet:boolean;
  js,apiurl,relativeurl:string;
  i:integer;
  cursor,comment_id,line:string;
begin
try
  Ffilename:=Fsavedir+'\'+Ftitle+'.txt';         //保存评论数据的文件名
  apiurl:=format(XHS_COMMON_API,[Fnoteid,'']);  //格式化请求url
  relativeurl:=getrelativeurl(apiurl);                        //相对链接url
  bRet:=getXsXt(relativeurl);                                   //对相对链接url签名
  if(bRet=false)then begin  log('getXsXt=失败');exit;end;

  js:=getRequestResult(apiurl,Fcookie,Fxs,Fxt,XHS_COMMON_REFER);   //发送https请求接口数据
  if(js='')then begin  log('js=空');exit;end;
  if(pos('成功',js)<=0)then
  begin
    log(js);               //记录返回的json日志数据
    exit;
  end;
  log(js);
  parseData(js);                       //解析json数据,取得评论数据
  while Fhasmore='true' do     //如果还有更多评论,继续请求数据
  begin
    apiurl:=format(XHS_COMMON_API,[Fnoteid,Fcursor]);  //格式化请求接口url,传入笔记id,cursor参数
    relativeurl:=GetRelativeUrl(apiUrl);                                 //相对链接签名
    bRet:=getXsXt(relativeurl);                                              //获取签名字段x-s,x-t
    if(not bRet)then begin  log('getXsXt=失败');continue;end;
    js:=getRequestResult(apiurl,uConfig.cookie,Fxs,Fxt,XHS_COMMON_REFER);   //发送https get 
    if(js='')then begin log('js=空');continue;end;
    if(pos('成功',js)<=0)then  begin log('js=失败'+js);exit;end;
    log(js);
    parseData(js);                                             //解析json评论数据
  end;
  Fmorecomment.SaveToFile(Fsavedir+'\morecomment.txt');
  for I := 0 to Fmorecomment.Count-1 do                          //如果有展开更多评论,继续请求更多评论接口
  begin
    line:=Fmorecomment[i];
    comment_id:=leftstr(line,24);//root_comment_id=640b7dad000000000700ded2
    cursor:=rightstr(line,24);//cursor=640bc4e4000000001500e610
    apiurl:=format(XHS_MORE_COMMON_API,[Fnoteid,comment_id,cursor]);//更多评论接口
    relativeurl:=GetRelativeUrl(apiUrl);
    bRet:=getXsXt(relativeurl);
    if(not bRet)then begin  log('getXsXt=失败');continue;end;
    js:=getRequestResult(apiurl,uConfig.cookie,Fxs,Fxt,XHS_COMMON_REFER);//请求更多评论接口数据
    if(js='')then begin log('js=空');continue;end;
    if(pos('成功',js)<=0)then  begin log('js=失败'+js);exit;end;
    log('morecomment='+js);
    parseMoreData(js);                             //解析更多评论json数据
  end;


finally
  Fcomment.SaveToFile(Ffilename,Tencoding.UTF8);         //保存完整的评论数据
  SendMessage(Fform,wm_downfile,3,integer(self));         //发送完成消息
end;
end;

三、完整源代码

unit uComment;

interface
uses
  windows,classes,System.Net.URLClient, System.Net.HttpClient, System.Net.HttpClientComponent,
  System.SysUtils,strutils,uLog,System.RegularExpressions,uFuncs,system.JSON,uConfig,
  uVideoInfo,uDownVideo,NetEncoding,ComObj,ActiveX;
const
  wm_user=$0400;
  wm_downfile=wm_user+100+1;
  XHS_COMMON_API:string='https://edith.xiaohongshu.com/api/sns/web/v2/comment/page?note_id=%s&cursor=%s';
  XHS_MORE_COMMON_API:string='https://edith.xiaohongshu.com/api/sns/web/v2/comment/sub/page?note_id=%s&root_comment_id=%s&num=10&cursor=%s';
  XHS_COMMON_REFER:string='https://www.xiaohongshu.com/';

type
  TComment=class(TThread)
   private
     FId:cardinal;
     Fnoteid,Ftitle:string;
     Fhasmore,Fcursor:string;
     Fxs,Fxt:string;
     Fsavedir,Ffilename:string;
     Fcomment,FmoreComment:tstrings;
     class var Fform: HWND;
     class var Fcookie: string;
     class procedure SetForm(const hForm: HWND); static;
     class procedure SetCookie(const cookie: string); static;
     procedure SetSaveDir(dir:string);
     procedure parseData(data:string);
     procedure parseMoreData(data:string);
     function JsonExist(parent:TJSONObject;child:string):boolean;
   protected
     procedure Execute; override;
   public
     constructor Create(id:cardinal;note_id,title:string);
     destructor Destroy;
     property id:cardinal read FId;
     class property form: HWND read Fform write SetForm;
     class property cookie: string read Fcookie write SetCookie;
     property savedir:string read Fsavedir write SetSaveDir;
     procedure working();
     function getDataFromQuery(note_id,cursor:string):string;
     function getXsXt(url:string):boolean;
     function GetRelativeUrl(url:string):string;
     function getRequestResult(apiurl:string;Cookie:string;xs,xt,refer:string):string;
     function getSubCommentCount(sub_Comment_cout:string):integer;
     function getIndex(s:string;ss:tstrings):integer;
  end;
implementation
//传入线程id号,笔记id,笔记标题
constructor TComment.Create(id:cardinal;note_id,title:string);
var
  line:string;
begin
  //inherited;
  //FreeOnTerminate := True;
  inherited Create(True);
  FId:=id;
  Fnoteid:=note_id;
  Ftitle:=uFuncs.formatFilename(title);
  Fhasmore:='false';
  Fcomment:=tstringlist.Create;
  line:='笔记ID='+note_id+'    标题:'+title;
  Fcomment.Add(line);
  Fmorecomment:=tstringlist.Create;
end;
destructor TComment.Destroy;
begin
  inherited Destroy;
  Fcomment.Free;
  Fmorecomment.free;
end;
//在子线程中运行
procedure TComment.Execute;
begin
  working();
end;
//主要流程
procedure TComment.working();
var
  bRet:boolean;
  js,apiurl,relativeurl:string;
  i:integer;
  cursor,comment_id,line:string;
begin
try
  Ffilename:=Fsavedir+'\'+Ftitle+'.txt';
  apiurl:=format(XHS_COMMON_API,[Fnoteid,'']);
  relativeurl:=getrelativeurl(apiurl);
  bRet:=getXsXt(relativeurl);
  if(bRet=false)then begin  log('getXsXt=失败');exit;end;

  js:=getRequestResult(apiurl,Fcookie,Fxs,Fxt,XHS_COMMON_REFER);
  if(js='')then begin  log('js=空');exit;end;
  if(pos('成功',js)<=0)then
  begin
    log(js);
    exit;
  end;
  log(js);
  parseData(js);
  while Fhasmore='true' do
  begin
    apiurl:=format(XHS_COMMON_API,[Fnoteid,Fcursor]);
    relativeurl:=GetRelativeUrl(apiUrl);
    bRet:=getXsXt(relativeurl);
    if(not bRet)then begin  log('getXsXt=失败');continue;end;
    js:=getRequestResult(apiurl,uConfig.cookie,Fxs,Fxt,XHS_COMMON_REFER);
    if(js='')then begin log('js=空');continue;end;
    if(pos('成功',js)<=0)then  begin log('js=失败'+js);exit;end;
    log(js);
    parseData(js);
  end;
  Fmorecomment.SaveToFile(Fsavedir+'\morecomment.txt');
  for I := 0 to Fmorecomment.Count-1 do
  begin
    line:=Fmorecomment[i];
    comment_id:=leftstr(line,24);//root_comment_id=640b7dad000000000700ded2
    cursor:=rightstr(line,24);//cursor=640bc4e4000000001500e610
    apiurl:=format(XHS_MORE_COMMON_API,[Fnoteid,comment_id,cursor]);
    relativeurl:=GetRelativeUrl(apiUrl);
    bRet:=getXsXt(relativeurl);
    if(not bRet)then begin  log('getXsXt=失败');continue;end;
    js:=getRequestResult(apiurl,uConfig.cookie,Fxs,Fxt,XHS_COMMON_REFER);
    if(js='')then begin log('js=空');continue;end;
    if(pos('成功',js)<=0)then  begin log('js=失败'+js);exit;end;
    log('morecomment='+js);
    parseMoreData(js);
  end;


finally
  Fcomment.SaveToFile(Ffilename,Tencoding.UTF8);
  SendMessage(Fform,wm_downfile,3,integer(self));
end;
end;


//获取相对链接
function TComment.GetRelativeUrl(url:string):string;
var
  i:integer;
  s:string;
begin
  result:='';
  if(url='')then exit;
  i:=pos('//',url);
  if(i<=0)then exit;
  s:=rightstr(url,length(url)-i-2);
  i:=pos('/',s);
  if(i<=0)then exit;
  s:=rightstr(s,length(s)-i+1);
  result:=s;
end;
//根据评论id号,获取评论序号
function TComment.getIndex(s:string;ss:tstrings):integer;
var
  i:integer;
  line:string;
begin
  result:=-1;
  for i := 0 to ss.Count-1 do
  begin
    line:=ss[i];
    if(s=line)then
    begin
      result:=i;
      exit;
    end;
  end;
end;
//子评论数量 
function TComment.getSubCommentCount(sub_Comment_cout:string):integer;
var
  s:string;
begin
  s:=sub_Comment_cout;
  s:=StringReplace(s,'"','',[rfReplaceAll]);
  result:=strtoint(s);
end;
//解析展开更多评论 数据
procedure TComment.parseMoreData(data:string);
var
  json,j1,j2,j3,j4,j5:TJSONObject;
  ja:TJSONArray;
  nickname:string;
  i,index:integer;
  user_id,content,line:string;
  comment_id:string;
  ss:tstrings;
begin
try
  ss:=tstringlist.Create;
  json := TJSONObject.ParseJSONValue(data) as TJSONObject;
  if json = nil then exit;
  j1:=json.GetValue('data') as TJSONObject;

  //Fhasmore:=j1.GetValue('has_more').Value;
  //if(Fhasmore='false')then exit;

  //Fcursor:=j1.GetValue('cursor').Value;

  ja:=j1.GetValue('comments') as TJSONArray;
  for I := 0 to ja.Size-1 do
  begin
    j2:=ja.Get(i) as TJSONObject;

    content:=j2.GetValue('content').Value;

    j3:=j2.GetValue('user_info') as TJSONObject;
    user_id:=j3.GetValue('user_id').Value;
    nickname:=j3.GetValue('nickname').Value;
    //comment_id:=j2.GetValue('id').Value;
    //fcomment.Add(line);
    line:='    '+user_id+'    '+nickname+'    '+content;
    ss.Add(line);
    j3:=j2.GetValue('target_comment') as TJSONObject;
    comment_id:=j3.GetValue('id').Value;
  end;
  index:=getIndex(comment_id,Fcomment);
  if(index>0)then
  begin
    Log(ss.Text);
    Fcomment.Insert(index+5,ss.Text);
  end;
finally
  if(json<>nil)then json.Free;
  ss.Free;
end;
end;
//解析评论json数据
procedure TComment.parseData(data:string);
var
  json,j1,j2,j3,j4,j5:TJSONObject;
  ja,ja1,sub_comments:TJSONArray;
  nickname:string;
  videoType:string;
  i,j,sub_comment_count_i:integer;
  video:TVideoInfo;
  note_id:string;
  sub_comment_has_more,user_id,content,line:string;
  comment_id,sub_comment_cursor,sub_comment_count:string;
begin
try
  json := TJSONObject.ParseJSONValue(data) as TJSONObject;
  if json = nil then exit;
  j1:=json.GetValue('data') as TJSONObject;
  Fhasmore:=j1.GetValue('has_more').Value;
  if(Fhasmore='false')then exit;

  Fcursor:=j1.GetValue('cursor').Value;

  ja:=j1.GetValue('comments') as TJSONArray;
  for I := 0 to ja.Size-1 do
  begin
    j2:=ja.Get(i) as TJSONObject;

    content:=j2.GetValue('content').Value;

    j3:=j2.GetValue('user_info') as TJSONObject;
    user_id:=j3.GetValue('user_id').Value;
    nickname:=j3.GetValue('nickname').Value;
    comment_id:=j2.GetValue('id').Value;
    fcomment.Add(comment_id);
    line:=user_id+'    '+nickname+'    '+content;
    fcomment.Add(line);

    sub_comment_has_more:=j2.GetValue('sub_comment_has_more').Value;

    if(sub_comment_has_more='true')then
    begin
      sub_comment_count:=j2.GetValue('sub_comment_count').Value;
      sub_comment_count_i:=getSubCommentCount(sub_comment_count);
      if(sub_comment_count_i>3)then
      begin
        sub_comment_cursor:=j2.GetValue('sub_comment_cursor').Value;
        FmoreComment.Add(comment_id+' '+sub_comment_cursor);
      end;
      sub_comments:=j2.GetValue('sub_comments') as TJSONArray;
      for j := 0 to sub_comments.size-1 do
      begin
        j3:=sub_comments.Get(j) as TJSONObject;
        content:=j3.GetValue('content').Value;
        j4:=j3.GetValue('user_info')  as TJSONObject;
        user_id:=j4.GetValue('user_id').Value;
        nickname:=j4.GetValue('nickname').Value;
        line:='    '+user_id+'    '+nickname+'    '+content;
        fcomment.Add(line);
      end;
    end;
  end;
finally
  if(json<>nil)then json.Free;
end;
end;
//判断节点是否存在
function TComment.JsonExist(parent:TJSONObject;child:string):boolean;
var
  i:integer;
  keyname:string;
begin
  result:=false;
  if(parent=nil)then exit;
  for i:=0 to parent.count-1 do
  begin
    keyname:=parent.Get(i).JsonString.toString;
    keyname:=midstr(keyname,2,length(keyname)-2);
   if(keyname=child)then
   begin
     result:=true;
     exit;
   end;
end;
end;
//发送https get 请求
function TComment.getRequestResult(apiurl:string;Cookie:string;xs,xt,refer:string):string;
var
  client: TNetHTTPClient;
  ss: TStringStream;
  s,id:string;
  AResponse:IHTTPResponse;
  i:integer;
begin
  result:='';
try
  client := TNetHTTPClient.Create(nil);
  SS := TStringStream.Create('',TEncoding.UTF8); //TEncoding.UTF8
  ss.Clear;
  with client do
  begin
    ConnectionTimeout := 30000; // 30秒
    ResponseTimeout := 30000; // 30秒
    AcceptCharSet := 'utf-8';
    UserAgent := USER_AGENT; //1
    client.AllowCookies:=true;
    client.HandleRedirects:=true;
    Accept:='application/json, text/plain, */*'; //'*/*'
    client.ContentType:='application/json'; //2
    client.AcceptLanguage:='zh-CN,zh;q=0.9';
    //client.AcceptEncoding:='gzip, deflate, br';
    client.CustomHeaders['Cookie'] := cookie;
    client.CustomHeaders['Referer'] := refer;
    client.CustomHeaders['X-s'] := xs;//签名
    client.CustomHeaders['X-t'] :=xt; //签名时间戳
    try
        AResponse:=Get(apiurl, ss);
        if(AResponse.StatusCode=200)then
        result:=ss.DataString;
    except
      on E: Exception do
        Log(e.Message);

    end;
  end;
finally
  ss.Free;
  client.Free;
end;

end;
//------------------------------------------属性方法-------------------------------------

 class procedure TComment.SetForm(const hForm: HWND);
 begin
   Fform:=hForm;
 end;
 class procedure TComment.SetCookie(const cookie: string);
 begin
   Fcookie:=cookie;
 end;
 procedure TComment.SetSaveDir(dir:string);
 begin
   Fsavedir:=dir;
 end;
end.


delphi 通过TNetHttpClient,获取小红书评论,解决x-s签名验证_数据采集




举报

相关推荐

nodejs爬虫小红书评论区

0 条评论