file_get_contents VS dom->loadHTMLFile - php

I've been making a PHP crawler that needs to get all links from a site and fire those links (instead of clicking it manually or doing client-side JS).
I have read these:
How do I make a simple crawler in PHP?
How do you parse and process HTML/XML in PHP?
and others more, and I decided to follow 1.
So far it has been working, but I have been baffled by the difference in the approach of using file_get_contents against dom->loadHTMLFile. Can you please enlighten me with these and the implications it might cause, pros and cons, or simple versus scenario.

Effectively these method are doing the same. However, using file_get_contents() you will need to store the results, at least temporarily, in a string variable unless you pass it to DOMDocument::loadHTML(). This leads to a higher memory usage in your application.
Some sites may require you to set some special header values, or use an other HTTP method than GET. If you need this, you need to specify a so called stream context. You can achieve this for both of the above methods using stream_context_create():
Example:
$opts = array(
'http'=>array(
'method'=>"GET",
'header'=>"Accept-language: en\r\n" .
"Cookie: foo=bar\r\n"
)
);
$ctx = stream_context_create($opts);
You can set this context using both of the above ways, but they differ in how to achieve this:
// With file_get_contents ...
$file_get_contents($url, false, $ctx);
// With DOM
libxml_set_streams_context($ctx);
$doc = new DOMDocument();
$doc->loadHTMLFile($url);
Leaves to be said, that using the curl extension you will have even more control about he HTTP transfer, what might be necessary in some special cases.

Related

When is a StreamContext reusable ? And when should it not be reused?

I'm passing from http to https, and therefore I have to add a StreamContext to several read_file and get_file_contents calls.
I need to replace
read_file('http://'.$host.$uri);
by
$stream_context = stream_context_create([
/* some lenghty options array */
]);
read_file('https://'.$host.$uri, false, $stream_context);
Now my question: Is a $stream_context reusable like this:
$stream_context = stream_context_create([
/* some lenghty options array */
]);
read_file('https://'.$host.$uri, false, $stream_context);
get_file_contents($another_url, false, $stream_context);
read_file($even_another, false, $stream_context);
or do I need to recreate a new StreamContext for each URL ?
Asked differently: Is a stream context just a descriptor for parameters and options, or does it get bound to the resource when using it ?
Edit: It seems from the comments, that one can reuse StreamContext often, but not always. This is not quite satisfactory as an answer.
When can or should it be reused, and when can't it be reused ? Can someone shed some light on the internal working of StreamContext. The documentation looks quite sparse to me.
stream contexts are re-usable and they can be re-used always, not often.
The comment from #ilpaijin pointing to "unpredicted behaviour comment" is simple a misunderstanding of the author leaving the comment.
When you specify your context for HTTP wrapper, you specify the wrapper as HTTP regardless of schema you are targeting, meaning there is no such thing as HTTPS wrapper.
If you try to do the following:
"https" => [
// options will not be applied to HTTPS stream as there is no such wrapper (https)
]
The correct way:
"http" => [
// options will apply to http:// and https:// streams.
]
When should/could re-use?
It's really up to you and up to the logic you are trying to implement.
Don't forget you have default context set for all native PHP wrappers.
The example you have posted where you have the same context stream being passed to 3 different call s is unnecessary, simple use stream_context_set_default and set the default context for request originating from your code.
There are certain situations where you set the default but for one particular request you want to have different context, this would be a good idea to create another stream and pass it in.
Does the stream context contain state, like for instance cookies or tls initial negotiation that are passes from one call to another?
Stream context does not contain state, however you could achieve a mock like this with additional code. Any state, let it be cookie or TLS handshake, are simply request headers. You would need to read that information from incoming request and set it in the stream, and then pass that stream to other request, thus mocking "the state" of parent request. That being said - don't do it, just use CURL.
On a side, the real power of streams is creating your own/custom stream. The header manipulation and state control are much easier (and better) achieved with CURL.
It apparently serves as a connection object (same logic like with database connection) and can be reused in a similar way:
<?php
$default_opts = array(
'http'=>array(
'method'=>"GET",
'header'=>"Accept-language: en\r\n" .
"Cookie: foo=bar",
'proxy'=>"tcp://10.54.1.39:8000"
)
);
$alternate_opts = array(
'http'=>array(
'method'=>"POST",
'header'=>"Content-type: application/x-www-form-urlencoded\r\n" .
"Content-length: " . strlen("baz=bomb"),
'content'=>"baz=bomb"
)
);
$default = stream_context_get_default($default_opts);
$alternate = stream_context_create($alternate_opts);
/* Sends a regular GET request to proxy server at 10.54.1.39
* For www.example.com using context options specified in $default_opts
*/
readfile('http://www.example.com');
/* Sends a POST request directly to www.example.com
* Using context options specified in $alternate_opts
*/
readfile('http://www.example.com', false, $alternate);
?>
It appears that you can. I used xdebug_debug_zval and ran some simple tests to see if PHP was retaining it internally (I used PHP 7.1.3 with xdebug on an internal development server)
$context = stream_context_create(['https' => ['method' => 'GET']]);
xdebug_debug_zval('context');
$stream = file_get_contents('https://secure.php.net/manual/en/function.file-get-contents.php', false, $context);
xdebug_debug_zval('context');
$stream = fopen('https://secure.php.net/', 'r', false, $context);
xdebug_debug_zval('context');
What I got back was
context:
(refcount=1, is_ref=0)resource(2, stream-context)
context:
(refcount=1, is_ref=0)resource(2, stream-context)
context:
(refcount=2, is_ref=0)resource(2, stream-context)
Interestingly, the second call increased the refcount, meaning it was passed by reference internally. Even unsetting $stream didn't remove it or prevent me from calling it again.
Edit
Since the question was modified...
A context creates a resource data type. Because it contains an instance of PHP data, it is passed by reference implicitly, meaning that PHP is passing the internal data directly and not simply making a copy of it. There's no native way to destroy a context.
I agree with above answers, stream_context_create() will create and return handle to resource by taking option parameters for a connection. This can be re-used to different resources, as it is a handle. Does not matter, where it is used but needs to have handle within the request.

Stream context in PHP - what is it?

I have searched for hours and I cannot figure out what a 'stream context' in PHP is. I'm trying to use an API and it involves using this 'stream context'.
The documentation says:
A context is a set of parameters and wrapper specific options which modify or enhance the behavior of a stream.
A parameter of what?
What is meant by an option being 'specific to a wrapper'?
What stream?
Here is the code I'm talking about:
// Encode the credentials and create the stream context.
$auth = base64_encode("$acctKey:$acctKey");
$data = array(
'http' => array(
'request_fulluri' => true,
// ignore_errors can help debug – remove for production. This option added in PHP 5.2.10
'ignore_errors' => true,
'header' => "Authorization: Basic $auth")
);
$context = stream_context_create($data);
// Get the response from Bing.
$response = file_get_contents($requestUri, 0, $context);
It took me a while to understand the stream contexts options and wrappers of PHP. I wrote an article about what helped me finally wrap my brain around how to understand PHP stream contexts options and wrappers. I hope it helps.
To properly handle whatever is coming down the line (streamed data), you will need the appropriate code to handle the different kinds of items being passed (data types). The tools for handling each different kind of data type are the “parameters”.
The “context” is determined by what is being pass along (streamed). So for different “contexts” (kinds of items) being “streamed” (passed along) the “parameters” (required tools for handling) the “data type” (kind of item) will change.
The term context simply makes reference to the fact that for different data types the situation is unique with its own required parameters.
The PHP stream wrapper would require a context in order to know which parameters are needed to handle the data type.
A parameter of the context that modifies the properties of the stream.
The options are specific to whatever wrapper the stream is using. Examples of these include files, all the different php:// URIs, the HTTP wrapper (like when you do file_get_contents('http://example.com') — it’s not the same thing as file_get_contents('some-file.txt'))
Any stream!
In this case, the stream context is passed to file_get_contents to tell it to send that authorization header and those options to the wrapper that allows file_get_contents to get contents from HTTP URLs.
You can find a list of the HTTP context options on the PHP website.
http, request_fulluri, ignore_errors, header are all parameters.
They change the way the function (file_get_contents in this case) works.
An option that is specific to a wrapper is something like 'http' --
you wouldn't use that on a filesystem file stream since it's not applicable.
The stream is the transfer of data itself which occurs when file_get_contents opens the connection, transfers everything, etc...

how to do http get request in PHP without PECL_HTTP or CURL

I am putting up website & the domain hosting (1&1) only provide base PHP 5.4 (No external libraries like PECL_HTTP or CURL etc, No installed libraries ).
I can add PHP class files to my code. I am trying to do a Http/Https get on this URL
$url ="https://www.googleapis.com/freebase/v1/search?query=chocolate&count=2";
I have tried Snoopy, & at least 6 different class libraries from PHPClasses none of them return anything (result is blank), I don't know why? But they all return page results.
Can anyone suggest a PHP class library that I can include (and NOT any installed library) which can do a simple Http/Https get & return results.
Your best bet for full http functionality (including headers, etc.) is to use file functions with a stream context.
From php.net:
$opts = array(
'http'=>array(
'method'=>"GET",
'header'=>"Accept-language: en\r\n" .
"Cookie: foo=bar\r\n"
)
);
$context = stream_context_create($opts);
$fp = fopen('http://www.example.com', 'r', false, $context);
fpassthru($fp);
fclose($fp)
If you just want the content of this "file" you could use file_get_contents() (see first example). See #OkekeEmmanuelOluchukwu's comment on how to send header.
Are you looking for the page output?
As you probably have found, include something.php?foo=bar does not work as php actually looks for a file ending in ?for=bar, but you can set all the get variables like this:
function getRequest($url, $data) {
foreach ($data as $key => $value) {
$_GET[$key] = $value;
}
include $url;
}
However, get variables on "full paths" (can't think of the proper name) work, eg include 'http://something.com/foo.php?bar=baz'. You can find examples here http://php.net/manual/en/function.include.php of the "variable passing" and get variables on a non-local-system.

PHP Magento Screen Scraping

I am trying to scrape a suppliers magento site in an effort to save some time because of there being around 2000 products I need to gather info for. I'm totally OK with writing a screen scraper for pretty much anything but i've encountered a major problem. Im using get_file_contentsto gather the html of the product page.
The problem is:
You need to be logged in, to view the product page. Its a standard magento login, so how can I get round this in my screen scraper? I don't require a full script, just advice on a method.
Using stream_context_create you can specify headers to be sent when calling your file_get_contents.
What I'd suggest is, open your browser and login to the site. Open up Firebug (or your favorite Cookie viewer) and grab the cookies and send them with your request.
Edit: Here's an example from PHP.net:
<?php
// Create a stream
$opts = array(
'http'=>array(
'method'=>"GET",
'header'=>"Accept-language: en\r\n" .
"Cookie: foo=bar\r\n"
)
);
$context = stream_context_create($opts);
// Open the file using the HTTP headers set above
$file = file_get_contents('http://www.example.com/', false, $context);
?>
Edit (2): This is out of the scope of your question, but if you are wondering how to scrape the website afterwards you could look into the DOMDocument::loadHTML method. This will essentially give you the required functions (i.e. XPath query, getElementsByTagName, getElementsById) to scrape what you need.
If you want to scrape something simple, you can also use RegEx with preg_match_all.
If you're familiar with CURL this should be relatively simple to do in a day or so. I've created some similar apps to login to banks to retrieve data - which of course also require authentication.
Below is a link with an example of how to use CURL with cookies for authentication purposes:
http://coderscult.com/php/php-curl/2008/05/20/php-curl-cookies-example/
If you can grab the output of the page you can parse for your results with a regex. Alternatively, you can use a class like Snoopy to do this work for you:
http://sourceforge.net/projects/snoopy/

What functions can I use to contact another webpage (to send GET data) and set a timeout with?

I need a function that I can use in my script to contact another script to send it some GET data. But I need to be able to set a timeout so that it only loads for a few seconds, then continues with the rest of the script. I know I could easily use cURL to do this, but I'd like to know if there are any alternatives?
You can specify a timeout for the standard file access functions (like file_get_contents()) using stream_context_create():
<?php
$opts = array(
'http'=>array(
'method'=>"GET",
'timeout' => 5
)
);
$context = stream_context_create($opts);
$fp = fopen('http://www.example.com', 'r', false, $context);
fpassthru($fp);
fclose($fp);
?>
See the list of context options for an explanation on the timeout option.
This requires, of course, that you can access external URLs using fopen() and consorts.
The nice thing about curl, is it lets you uses threads even though php doesn't support them. So you can make the call to curl_multi, give it a callback, and let the rest of the script run. This way your regular processing isn't blocked. This reduces the need for a short timeout.

Categories