尝试从图像 URL 下载图像,但我得到的是 HTML
Try to download image from image URL, but I get HTML instead
我有 URL https://art42.tumblr.com/random
到显示一些图像的网页。我想从该页面下载主图像。
如果我在 Firefox(或任何其他浏览器)中右键单击图像并选择“在新选项卡中打开图像”,我会得到图像 (jpg)。 但是,如果我尝试使用下面的代码下载图像,我会得到一个 HTML 文件。
我猜问题与Referer有关。我尝试将 "Referer"
参数设置为页面的 URL 以及图像的 URL,但我仍然得到 HTML 而不是 JPG。
为什么我可以在 Firefox 中下载图片,但在我的代码中却不能?
function DownloadFile(CONST Url, Referer: String; OUT Data: TBytes; PostData: String= ''; SSL: Boolean = FALSE): Boolean; { TESTED OK }
VAR
Buffer : array[0..High(Word)*4] of Byte; { Buffer of 260KB }
TempBytes : TBytes;
sMethod : string;
BytesRead : Cardinal;
pSession : HINTERNET;
pConnection: HINTERNET;
pRequest : HINTERNET;
Resource : string;
Root : string;
port : Integer;
flags : DWord;
Header : string;
begin
Result := FALSE;
SetLength(Data, 0);
pSession := InternetOpen(nil {USER_AGENT}, INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
if Assigned(pSession) then
TRY
{ Autodetect port }
port:= UrlExtractPort(URL);
if port = 0 then
if SSL
then Port := INTERNET_DEFAULT_HTTPS_PORT
else Port := INTERNET_DEFAULT_HTTP_PORT;
{ Root }
Root:= UrlExtractDomainRelaxed(Url);
pConnection := InternetConnect(pSession, PWideChar(Root), port, nil, nil, INTERNET_SERVICE_HTTP, 0, 0); { The second parameter of InternetConnect should contain only the name of the server, not the entire URL of the server-side script. }
if Assigned(pConnection) then
TRY
if (PostData = '')
then sMethod := 'GET'
else sMethod := 'POST';
if SSL
then flags := INTERNET_FLAG_SECURE OR INTERNET_FLAG_KEEP_CONNECTION
else flags := INTERNET_SERVICE_HTTP OR INTERNET_FLAG_RELOAD; // INTERNET_FLAG_RELOAD= Forces a download of the requested file, object, or directory listing from the origin server, not from the cache.;
Resource := UrlExtractResourceParams(Url);
pRequest := HTTPOpenRequest(pConnection, PWideChar(sMethod), PWideChar(Resource), nil, nil, nil, flags, 0); { The third parameter of HttpOpenRequest is the file name (URL) of the script }
if Assigned(pRequest) then
TRY
Header:= '';
if Referer > ''
then Header:= Header+ 'Referer: ' + Referer + sLineBreak;
Header:= Header+ 'User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0'+SLineBreak;
//Header:= Header+ 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.59'+SLineBreak; // Microsoft Edge UA string
Header:= Header+ 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'+SLineBreak;
Header:= Header+ 'Accept-Language: en-us,en;q=0.5' + SLineBreak;
Header:= Header+ 'Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7'+SLineBreak;
Header:= Header+ 'Keep-Alive: 70'+ SLineBreak; { In windows, default is 60 sec }
Header:= Header+ 'Connection: keep-alive'+ SlineBreak+SLineBreak;
HttpAddRequestHeaders(pRequest, PWideChar(Header), Length(Header), HTTP_ADDREQ_FLAG_ADD);
Result:= HTTPSendRequest(pRequest, NIL, 0, Pointer(PostData), Length(PostData)); { The actual POST data is the forth parameter }
if Result then
REPEAT
ZeroMemory(@Buffer, SizeOf(Buffer));
{ Download bytes }
InternetReadFile(pRequest, @Buffer, SizeOf(Buffer), BytesRead);
{ We stop? }
if BytesRead= 0 then break;
{ Convert static array to dynamic array }
SetLength(TempBytes, BytesRead);
Move(Buffer[0], TempBytes[0], BytesRead);
{ Merge arrays }
Data:= Data+ TempBytes;
UNTIL BytesRead= 0;
FINALLY
InternetCloseHandle(pRequest);
END
else
RaiseLastOSError;
finally
InternetCloseHandle(pConnection);
end;
finally
InternetCloseHandle(pSession);
end;
end;
在您的“接受”中 header 您没有指定任何图形格式。
正确配置的 Web 服务器应该以您接受的格式之一向您发送资源的表示形式。
尝试查看从您的浏览器发送的请求 header(使用开发人员工具 - 请参阅浏览器的帮助以了解如何访问它们)。它将接受图形格式和文本。
它按预期工作:
您请求 GET https://art42.tumblr.com/random
,得到的答复是 HTTP 302 Found
。
该响应意味着答案中必须有一个 header Location
,它指向我们应该查询的新 URL。在我的例子中,整个 header 是:
Location: https://art42.tumblr.com/post/158825454869#_=_
URL 是一个新的 GET
请求,最终得到 HTTP 200 OK
的答复。
这个响应主要意味着我们有一些有效负载,header Content-Type
应该可以帮助我们以正确的方式处理它。在我的例子中,整个 header 是:
Content-Type: text/html; charset=UTF-8
这意味着它是具有 HTML 内容的文本文档,以 UTF-8 编码。太好了 - 需要解析 PDF 或 EXE 将变得不那么琐碎。
就是这样:一切都按预期进行。它仍然是一个 网站 - 你甚至可以通过周围的所有文字看到这一点。仅仅因为 嵌入了一张图片 并不能使整个负载也成为一张图片。
如果您不能区分网站和图片,那么您将有很长的路要走。 Web 浏览器可以显示各种媒体:解析为 HTML 呈现的网站、各种格式的视频和图片、文本文件,现在甚至是 PDF... 图片的 URL 将是 https://64.media.tumblr.com/0b37315236ee5da6cb4d191ea6a14ccb/tumblr_on2uab8Anw1vb29w2o1_500.jpg
,当然可以在 HTML 中找到。如果您可以在图片上 right-click 保存它,您还可以在新标签页中显示它 - 与您当前正在查看的网站不同 的显示方式刚刚嵌入。
是的:你应该解析整个HTML,注意所有遇到的<img src="
,然后检查这是否是你想要的图片。幸运的是它更容易:只需搜索 <meta name="twitter:image" content="
然后复制所有内容直到 " />
以获得您的实际图片 URL.
编辑:为了(不)重现 OP 的问题,此代码已优化为 运行,并且还会自动加载图片。请注意,许多有问题的自定义已被删除,尤其是 headers 和引用者,格式更加一致:
uses
wininet, jpeg;
function DownloadFile(Data: TMemoryStream): Boolean;
var
Buffer: Array[0.. High(Word)* 4] of Byte;
Resource, Root, sMethod: AnsiString;
BytesRead, flags: Cardinal;
pSession, pConnection, pRequest: HINTERNET;
port: Word;
begin
Result:= FALSE;
Data.Clear;
pSession:= InternetOpenA(nil, INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
if Assigned(pSession) then
try
port:= 443;
Root:= '64.media.tumblr.com';
pConnection:= InternetConnectA(pSession, PAnsiChar(Root), port, nil, nil, INTERNET_SERVICE_HTTP, 0, 0);
if Assigned(pConnection) then
try
sMethod:= 'GET';
flags:= INTERNET_FLAG_SECURE or INTERNET_FLAG_KEEP_CONNECTION;
Resource:= '/0b37315236ee5da6cb4d191ea6a14ccb/tumblr_on2uab8Anw1vb29w2o1_500.jpg';
pRequest:= HTTPOpenRequestA(pConnection, PAnsiChar(sMethod), PAnsiChar(Resource), nil, nil, nil, flags, 0);
if Assigned(pRequest) then
try
Result:= HTTPSendRequestA(pRequest, nil, 0, nil, 0);
if Result then
repeat
InternetReadFile(pRequest, @Buffer, SizeOf(Buffer), BytesRead);
if BytesRead= 0 then break;
Data.Write(Buffer[0], BytesRead);
until FALSE;
finally
InternetCloseHandle(pRequest);
end
else RaiseLastOSError;
finally
InternetCloseHandle(pConnection);
end;
finally
InternetCloseHandle(pSession);
end;
end;
// Actually executing it: just add one TImage to your form
procedure TForm1.Button1Click(Sender: TObject);
var
Data: TMemoryStream;
j: TJpegImage;
Head: AnsiString;
begin
Data:= TMemoryStream.Create;
DownloadFile(Data);
if Data.Size> 3 then begin // Reasonable size for picture
Data.Position:= 0;
SetLength(Head, 3);
Data.Read(Head[1], 3);
if Head= #$ff#$d8#$ff then // Is it JFIF (aka JPG)?
begin
Data.Position:= 0;
j:= TJpegImage.Create;
try
j.LoadFromStream(Data);
Image1.AutoSize:= TRUE;
Image1.Picture.Assign(j);
except
// Might be corrupt or its (sub) format is not supported
end;
j.Free;
end;
end;
Data.Free;
end;
我有 URL https://art42.tumblr.com/random
到显示一些图像的网页。我想从该页面下载主图像。
如果我在 Firefox(或任何其他浏览器)中右键单击图像并选择“在新选项卡中打开图像”,我会得到图像 (jpg)。 但是,如果我尝试使用下面的代码下载图像,我会得到一个 HTML 文件。
我猜问题与Referer有关。我尝试将 "Referer"
参数设置为页面的 URL 以及图像的 URL,但我仍然得到 HTML 而不是 JPG。
为什么我可以在 Firefox 中下载图片,但在我的代码中却不能?
function DownloadFile(CONST Url, Referer: String; OUT Data: TBytes; PostData: String= ''; SSL: Boolean = FALSE): Boolean; { TESTED OK }
VAR
Buffer : array[0..High(Word)*4] of Byte; { Buffer of 260KB }
TempBytes : TBytes;
sMethod : string;
BytesRead : Cardinal;
pSession : HINTERNET;
pConnection: HINTERNET;
pRequest : HINTERNET;
Resource : string;
Root : string;
port : Integer;
flags : DWord;
Header : string;
begin
Result := FALSE;
SetLength(Data, 0);
pSession := InternetOpen(nil {USER_AGENT}, INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
if Assigned(pSession) then
TRY
{ Autodetect port }
port:= UrlExtractPort(URL);
if port = 0 then
if SSL
then Port := INTERNET_DEFAULT_HTTPS_PORT
else Port := INTERNET_DEFAULT_HTTP_PORT;
{ Root }
Root:= UrlExtractDomainRelaxed(Url);
pConnection := InternetConnect(pSession, PWideChar(Root), port, nil, nil, INTERNET_SERVICE_HTTP, 0, 0); { The second parameter of InternetConnect should contain only the name of the server, not the entire URL of the server-side script. }
if Assigned(pConnection) then
TRY
if (PostData = '')
then sMethod := 'GET'
else sMethod := 'POST';
if SSL
then flags := INTERNET_FLAG_SECURE OR INTERNET_FLAG_KEEP_CONNECTION
else flags := INTERNET_SERVICE_HTTP OR INTERNET_FLAG_RELOAD; // INTERNET_FLAG_RELOAD= Forces a download of the requested file, object, or directory listing from the origin server, not from the cache.;
Resource := UrlExtractResourceParams(Url);
pRequest := HTTPOpenRequest(pConnection, PWideChar(sMethod), PWideChar(Resource), nil, nil, nil, flags, 0); { The third parameter of HttpOpenRequest is the file name (URL) of the script }
if Assigned(pRequest) then
TRY
Header:= '';
if Referer > ''
then Header:= Header+ 'Referer: ' + Referer + sLineBreak;
Header:= Header+ 'User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0'+SLineBreak;
//Header:= Header+ 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.59'+SLineBreak; // Microsoft Edge UA string
Header:= Header+ 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'+SLineBreak;
Header:= Header+ 'Accept-Language: en-us,en;q=0.5' + SLineBreak;
Header:= Header+ 'Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7'+SLineBreak;
Header:= Header+ 'Keep-Alive: 70'+ SLineBreak; { In windows, default is 60 sec }
Header:= Header+ 'Connection: keep-alive'+ SlineBreak+SLineBreak;
HttpAddRequestHeaders(pRequest, PWideChar(Header), Length(Header), HTTP_ADDREQ_FLAG_ADD);
Result:= HTTPSendRequest(pRequest, NIL, 0, Pointer(PostData), Length(PostData)); { The actual POST data is the forth parameter }
if Result then
REPEAT
ZeroMemory(@Buffer, SizeOf(Buffer));
{ Download bytes }
InternetReadFile(pRequest, @Buffer, SizeOf(Buffer), BytesRead);
{ We stop? }
if BytesRead= 0 then break;
{ Convert static array to dynamic array }
SetLength(TempBytes, BytesRead);
Move(Buffer[0], TempBytes[0], BytesRead);
{ Merge arrays }
Data:= Data+ TempBytes;
UNTIL BytesRead= 0;
FINALLY
InternetCloseHandle(pRequest);
END
else
RaiseLastOSError;
finally
InternetCloseHandle(pConnection);
end;
finally
InternetCloseHandle(pSession);
end;
end;
在您的“接受”中 header 您没有指定任何图形格式。
正确配置的 Web 服务器应该以您接受的格式之一向您发送资源的表示形式。
尝试查看从您的浏览器发送的请求 header(使用开发人员工具 - 请参阅浏览器的帮助以了解如何访问它们)。它将接受图形格式和文本。
它按预期工作:
您请求
GET https://art42.tumblr.com/random
,得到的答复是HTTP 302 Found
。该响应意味着答案中必须有一个 header
Location
,它指向我们应该查询的新 URL。在我的例子中,整个 header 是:Location: https://art42.tumblr.com/post/158825454869#_=_
URL 是一个新的
GET
请求,最终得到HTTP 200 OK
的答复。这个响应主要意味着我们有一些有效负载,header
Content-Type
应该可以帮助我们以正确的方式处理它。在我的例子中,整个 header 是:Content-Type: text/html; charset=UTF-8
这意味着它是具有 HTML 内容的文本文档,以 UTF-8 编码。太好了 - 需要解析 PDF 或 EXE 将变得不那么琐碎。
就是这样:一切都按预期进行。它仍然是一个 网站 - 你甚至可以通过周围的所有文字看到这一点。仅仅因为 嵌入了一张图片 并不能使整个负载也成为一张图片。
如果您不能区分网站和图片,那么您将有很长的路要走。 Web 浏览器可以显示各种媒体:解析为 HTML 呈现的网站、各种格式的视频和图片、文本文件,现在甚至是 PDF... 图片的 URL 将是 https://64.media.tumblr.com/0b37315236ee5da6cb4d191ea6a14ccb/tumblr_on2uab8Anw1vb29w2o1_500.jpg
,当然可以在 HTML 中找到。如果您可以在图片上 right-click 保存它,您还可以在新标签页中显示它 - 与您当前正在查看的网站不同 的显示方式刚刚嵌入。
是的:你应该解析整个HTML,注意所有遇到的<img src="
,然后检查这是否是你想要的图片。幸运的是它更容易:只需搜索 <meta name="twitter:image" content="
然后复制所有内容直到 " />
以获得您的实际图片 URL.
编辑:为了(不)重现 OP 的问题,此代码已优化为 运行,并且还会自动加载图片。请注意,许多有问题的自定义已被删除,尤其是 headers 和引用者,格式更加一致:
uses
wininet, jpeg;
function DownloadFile(Data: TMemoryStream): Boolean;
var
Buffer: Array[0.. High(Word)* 4] of Byte;
Resource, Root, sMethod: AnsiString;
BytesRead, flags: Cardinal;
pSession, pConnection, pRequest: HINTERNET;
port: Word;
begin
Result:= FALSE;
Data.Clear;
pSession:= InternetOpenA(nil, INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
if Assigned(pSession) then
try
port:= 443;
Root:= '64.media.tumblr.com';
pConnection:= InternetConnectA(pSession, PAnsiChar(Root), port, nil, nil, INTERNET_SERVICE_HTTP, 0, 0);
if Assigned(pConnection) then
try
sMethod:= 'GET';
flags:= INTERNET_FLAG_SECURE or INTERNET_FLAG_KEEP_CONNECTION;
Resource:= '/0b37315236ee5da6cb4d191ea6a14ccb/tumblr_on2uab8Anw1vb29w2o1_500.jpg';
pRequest:= HTTPOpenRequestA(pConnection, PAnsiChar(sMethod), PAnsiChar(Resource), nil, nil, nil, flags, 0);
if Assigned(pRequest) then
try
Result:= HTTPSendRequestA(pRequest, nil, 0, nil, 0);
if Result then
repeat
InternetReadFile(pRequest, @Buffer, SizeOf(Buffer), BytesRead);
if BytesRead= 0 then break;
Data.Write(Buffer[0], BytesRead);
until FALSE;
finally
InternetCloseHandle(pRequest);
end
else RaiseLastOSError;
finally
InternetCloseHandle(pConnection);
end;
finally
InternetCloseHandle(pSession);
end;
end;
// Actually executing it: just add one TImage to your form
procedure TForm1.Button1Click(Sender: TObject);
var
Data: TMemoryStream;
j: TJpegImage;
Head: AnsiString;
begin
Data:= TMemoryStream.Create;
DownloadFile(Data);
if Data.Size> 3 then begin // Reasonable size for picture
Data.Position:= 0;
SetLength(Head, 3);
Data.Read(Head[1], 3);
if Head= #$ff#$d8#$ff then // Is it JFIF (aka JPG)?
begin
Data.Position:= 0;
j:= TJpegImage.Create;
try
j.LoadFromStream(Data);
Image1.AutoSize:= TRUE;
Image1.Picture.Assign(j);
except
// Might be corrupt or its (sub) format is not supported
end;
j.Free;
end;
end;
Data.Free;
end;