启动时等待验证码.

输入验证码后,等待处理数据

处理完成后,显示数据

实现原理:利用已有的账号和密码登录后产生的cookie,向服务器请求数据.返回的数据是JSON格式,后期需要自己整理.
注意,请在程序中使用同一个TnetHttpClient控件来完成.因为里面保存了cookie信息

需要了解的知识点:
一. token:
在 Web 开发中,Token 经常用于用户身份验证。当用户成功登录后,服务器会生成一个 Token,并将其返回给客户端。客户端将该 Token 存储在本地,以后的请求都携带该 Token,用于证明请求的合法性。服务器会根据 Token 验证用户身份,并决定是否允许该请求访问相关资源。
在登录页面的源代码中,按 ctrl+F 查找 token

二.cookie
![]()
Cookie 主要用于跟踪用户的状态和行为,以提供个性化的用户体验。通过存储在 Cookie 中的数据,网站可以识别和区分不同的用户,记录用户的偏好设置,保持用户登录状态,以及追踪用户在网站上的访问轨迹等。这些信息可以在用户下次访问网站时被读取并使用。 Cookie 有两种类型:会话性 Cookie 和持久性 Cookie。 会话性 Cookie 存储在用户的计算机上,但在用户关闭浏览器时会被自动删除。这种类型的 Cookie 通常用于临时存储会话信息,如用户的登录状态。 持久性 Cookie 有一个指定的过期日期,可以在用户关闭浏览器后继续存在。这种类型的 Cookie 可以用于记住用户的偏好设置和提供个性化的内容。
![]()
三.Referer
Referer 是一个 HTTP 请求头字段,提供了当前请求的来源信息,用于统计、安全验证和针对性处理等用途。但由于用户隐私的考虑,Referer 值可能为空或被限制。
四.数据的请求地址.就是向服务器请求数据的地址.
下面的动画中,展示了登录网站后,如何获取Referer和数据请求地址的过程.
第一步就是登陆了,
第二步是转到要下载文件的页面,按F12调出开发者工具.红3是清除当前记录,避免干扰

第三步.点击下载文件,然后会看到出现两条记录(以我当面的页面为例).我们观察第二个地址.里面有limit,明显是分页的,舍去,取第一个地址.
也就是数据请求的地址找到了. 等于号后面的那串13位数字,我后面会讲

第四步,单击第一条记录,可以看到它的标头信息,里面就有Referer 信息,记下来.其实为了保险起见,标头里的信息应该全部封装到代码里才算完美.

点击预览选项卡,这里你就可以看到你的数据包了,我们要抓的,就是这个数据.


最后我们再回头看看这一串13位的数字.它其实是一个13位的时间戳,它是给服务器用来校验请求的时间是否在合法范围内.知道它是什么,要做一个就简单了

下面是完整的程序代码:
![]()
type
TForm1 = class(TForm)
Image1: TImage;
Label1: TLabel;
NetHTTPClient1: TNetHTTPClient;
scGPEdit1: TscGPEdit;
cxGrid1Level1: TcxGridLevel;
cxGrid1: TcxGrid;
cxGrid1TableView1: TcxGridTableView;
cxGrid1TableView1Column1: TcxGridColumn;
cxGrid1TableView1Column2: TcxGridColumn;
cxGrid1TableView1Column3: TcxGridColumn;
cxGrid1TableView1Column4: TcxGridColumn;
cxGrid1TableView1Column5: TcxGridColumn;
scGPPanel1: TscGPPanel;
scGPPanel2: TscGPPanel;
procedure FormCreate(Sender: TObject);
procedure Image1Click(Sender: TObject);
procedure scGPEdit1RightButtonClick(Sender: TObject);
procedure scGPEdit1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
private
{ Private declarations }
public
{ Public declarations }
end;
TMyThread = class(TThread)
protected
procedure Execute; override;
end;
var
Form1: TForm1;
implementation
uses
DateUtils;
{$R *.dfm}
// 取13位数字时间
function GetDigitalTime: string;
var
S_Char: string;
begin
S_Char := FormatSettings.DateSeparator;
Result := IntToStr(MilliSecondsBetween(IncHour(now, -8), StrToDateTime('1970'
+ S_Char + '1' + S_Char + '1')));
end;
procedure LoadTxtToCxGrid(); //把处理好的数据,加载进表格,展示给用户
begin
var Lines := TStringList.Create;
try
Lines.LoadFromFile('d:\resule.txt', TEncoding.UTF8);
var view := form1.cxGrid1TableView1.DataController;
for var i := 1 to Lines.Count - 1 do
begin
var k := view.AppendRecord;
var arr: TArray<string> := Lines[i].Split([',']);
for var j := 0 to 4 do
begin
view.Values[k, j] := arr[j]; //这里一定要注意.先在cxgrid中创建好列,这里是五列,所以才写了0 to 4 .群里有个老六,就为这点喷我,搞得很不愉快.
end;
end;
//隐藏验证码部分,显示出数据表
Form1.scGPPanel1.Visible := False;
Form1.Height := 300;
Form1.scGPPanel2.Visible := True;
Form1.scGPPanel2.Align := alClient;
finally
Lines.Free;
DeleteFile('D:\123.txt'); //删除文件
DeleteFile('d:\resule.txt'); //删除文件
end;
end;
function washData(): Boolean; //清洗数据
var
Reader: TStreamReader;
Writer: TStreamWriter;
txts: string;
JSONObj: TJSONObject;
StockValue, GoodsNameValue: string;
JsonArray: TJSONArray;
begin
Reader := TStreamReader.Create('D:\123.txt');
Writer := TStreamWriter.Create('D:\resule.txt');
try
txts := Reader.ReadToEnd; // 读取全部的文件内容
JSONObj := TJSONObject.ParseJSONValue(txts) as TJSONObject;
try
if Assigned(JSONObj) then
begin
JsonArray := JSONObj.GetValue('rows') as TJSONArray; //选对节点,才能拿到正确数据
Writer.WriteLine('物料代码,物料名称,规格型号,单位,库存');
for var i := 0 to JsonArray.Count - 1 do
begin
StockValue := JsonArray.Items[i].GetValue<string>('stock'); // 获取 stock 字段值
GoodsNameValue := JsonArray.Items[i].GetValue<string>('goods_name'); // 获取 goods_name 字段值
// 输出数据
if GoodsNameValue <> '' then
begin
GoodsNameValue := GoodsNameValue.Replace('\', '', [rfReplaceAll]); //去掉所有'\'
GoodsNameValue := GoodsNameValue.Replace('刀', '刀/', [rfReplaceAll]);
GoodsNameValue := GoodsNameValue.Replace('丝锥', '丝锥/', [rfReplaceAll]);
GoodsNameValue := GoodsNameValue.Replace('钻D', '钻/D', [rfReplaceAll]);
GoodsNameValue := GoodsNameValue.Replace('钻头', '钻头/', [rfReplaceAll]);
var arr: tarray<string>;
arr := GoodsNameValue.Split(['/']);
Writer.WriteLine(arr[0] + ',' + arr[1] + ',' + arr[2] + ',' + arr[3]
+ ',' + StockValue);
end;
end;
end;
finally
JSONObj.Free;
end;
finally
Reader.Free;
Writer.Free;
//写入表格
LoadTxtToCxGrid;
end;
end;
procedure TForm1.scGPEdit1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
if Key = 13 then
scGPEdit1RightButtonClick(Sender);
end;
procedure TForm1.scGPEdit1RightButtonClick(Sender: TObject);
var
ss_Request: TStringList;
token: string;
captcha: string;
match: TMatch;
S2, S3: TMemoryStream;
Resp: IHTTPResponse;
const
// 登陆页面
TokenURL = '登陆地址';
// 提交网址
PostURL = '登陆地址';
begin
//封装标头信息
NetHTTPClient1.UserAgent :=
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36';
NetHTTPClient1.Accept := 'application/json, text/javascript, */*; q=0.01';
NetHTTPClient1.ContentType := 'application/json';
captcha := Trim(scGPEdit1.text); // 用户输入的验证码
if Length(captcha) <> 4 then // 初步判断用户输入的验证码长度是否正确
begin
ShowMessage('请输入正确的验证码');
Exit;
end;
// --------获取token ---------
Resp := NetHTTPClient1.Get(TokenURL); // 取得登陆页面的源代码
// -------用正则取出token
match := TRegEx.match(Resp.ContentAsString(TEncoding.UTF8), '[\w]{32}');
if match.Success then
begin
token := match.Groups[0].Value;
end
else
begin
ShowMessage('没有找到token');
Exit;
end;
// --------封包---------
ss_Request := TStringList.Create;
ss_Request.Add('__token__=' + token);
ss_Request.Add('username=登陆的用户名');
ss_Request.Add('password=登陆密码');
ss_Request.Add('captcha=' + scgpedit1.text); //scgpedit1.text 是验证码,这个需要手动输入
// resp 提交登陆请求
Resp := NetHTTPClient1.Post(PostURL, ss_Request);
if Resp.StatusCode = 200 then //如果登陆成功,则开始抓包
begin
Sleep(500);
NetHTTPClient1.CustomHeaders['X-Requested-With'] := 'XMLHttpRequest';
NetHTTPClient1.CustomHeaders['Referer'] := 'xxxx';
S2 := TMemoryStream.Create;
NetHTTPClient1.Get('数据请求的地址'+ GetDigitalTime, S2);
S2.Position := 0;
S2.SaveToFile('d:\123.txt'); //把流数据保存成文件,以便后期处理
scGPEdit1.text := '稍等,处理中...';
scGPEdit1.Enabled := false;
washData; //自定义函数,整理123.txt中的内容为需要的格式
end
else
ShowMessage('请求失败');
end;
//初始化窗口.开始时只显示验证码部分
procedure TForm1.FormCreate(Sender: TObject);
begin
scGPPanel2.Visible := false;
Self.Height := 120;
scGPEdit1.Text := '请等待...';
scGPEdit1.Enabled := False;
Image1Click(Sender);
end;
procedure TForm1.Image1Click(Sender: TObject); // 点击图片时更新验证码
var
MyThread: TMyThread;
begin
// 创建并启动自定义线程
MyThread := TMyThread.Create(true);
MyThread.FreeOnTerminate := true; // 线程执行结束后自动释放
MyThread.Start;
end;
{ TMyThread }
procedure TMyThread.Execute;
var
Stream: TMemoryStream;
pngimage: TPngImage;
begin
Stream := TMemoryStream.Create;
pngimage := TPngImage.Create;
try
Form1.NetHTTPClient1.Get('验证码地址', Stream); // 下载验证码
Stream.Position := 0; //初始化读写位置
pngimage.LoadFromStream(Stream); // 转换流
Form1.Image1.Picture.Bitmap.Assign(pngimage); // 把验证码显示出来
if Form1.Image1.Picture.Bitmap = nil then
ShowMessage('验证码加载失败,请点击右边的图片重新加载')
else
begin
Form1.scGPEdit1.Enabled := true;
Form1.scGPEdit1.text := '';
end;
finally
pngimage.Free;
Stream.Free;
end;
end;
end.



















