这个问题听起来很简单,但并不像听起来那么简单。
错误摘要
例如,使用此板; http://pinterest.com/dodo/web-designui-and-mobile/
在页面顶部检查电路板本身的HTML(位于div
内的类GridItems
)会产生:
<div class="variableHeightLayout padItems GridItems Module centeredWithinWrapper" style="..">
<!-- First div with a displayed board image -->
<div class="item" style="top: 0px; left: 0px; visibility: visible;">..</div>
...
<!-- Last div with a displayed board image -->
<div class="item" style="top: 3343px; left: 1000px; visibility: visible;">..</div>
</div>
然而,在页面底部,在激活无限滚动几次之后,我们将其作为HTML:
<div class="variableHeightLayout padItems GridItems Module centeredWithinWrapper" style="..">
<!-- First div with a displayed board image -->
<div class="item" style="top: 12431px; left: 750px; visibility: visible;">..</div>
...
<!-- Last div with a displayed board image -->
<div class="item" style="top: 19944px; left: 750px; visibility: visible;">..</div>
</div>
正如您所看到的,页面上方图像的某些容器已经消失,并且在首次加载页面时并非所有图像容器都会加载。
我想做什么
我希望能够创建一个可以下载页面完整HTML的C#脚本(或任何服务器端语言)(即检索页面上的每个图像),然后从他们的页面下载图像网址。下载网页并使用适当的XPath很容易,但真正的挑战是为每个图像下载完整的HTML。
有没有办法可以模拟滚动到页面底部,还是有更简单的方法可以检索每个图像?我想Pinterest使用AJAX来改变HTML,有没有办法可以通过编程方式触发事件来接收所有的HTML?提前感谢您的建议和解决方案,并且如果您没有任何建议和解决方案,请立即阅读这个非常长的问题!
伪码
using System;
using System.Net;
using HtmlAgilityPack;
private void Main() {
string pinterestURL = "http://www.pinterest.com/...";
string XPath = ".../img";
HtmlDocument doc = new HtmlDocument();
// Currently only downloads the first 25 images.
doc.Load(strPinterestUrl);
foreach(HtmlNode link in doc.DocumentElement.SelectNodes(strXPath))
{
image_links[] = link["src"];
// Use image links
}
}
答案 0 :(得分:2)
好的,所以我认为这可能(有一些改动)你需要的东西。
<强>注意事项:强>
兴趣点:
_
参数采用JavaScript格式的时间戳,即。像Unix时间,但它添加了毫秒。它实际上并不用于分页。bookmarks
属性,因此您向“新”端点发出第一个请求,但不需要它,然后从结果中获取bookmarks
并在请求中使用它要获取结果的下一个“页面”,请从这些结果中取bookmarks
以获取下一页,然后依此类推,直到您的结果用完或达到预设限制(或者您点击服务器)最大脚本执行时间)。我很想知道bookmarks
字段编码的具体内容。我想除了针脚ID或其他页面标记外,还有一些有趣的秘诀。<?php
if(!class_exists('Skrivener_Pins')) {
class Skrivener_Pins {
/**
* Constructor
*/
public function __construct() {
}
/**
* Pinterest search function. Uses Pinterest's "internal" page APIs, so likely to break if they change.
* @author [@skrivener] Philip Tillsley
* @param $search_str The string used to search for matching pins.
* @param $limit Max number of pages to get, defaults to 2 to avoid excessively large queries. Use care when passing in a value.
* @param $bookmarks_str Used internally for recursive fetches.
* @param $pages Used internally to limit recursion.
* @return array() int['id'], obj['image'], str['pin_link'], str['orig_link'], bool['video_flag']
*
* TODO:
*
*
*/
public function get_tagged_pins($search_str, $limit = 1, $bookmarks_str = null, $page = 1) {
// limit depth of recursion, ie. number of pages of 25 returned, otherwise we can hang on huge queries
if( $page > $limit ) return false;
// are we getting a next page of pins or not
$next_page = false;
if( isset($bookmarks_str) ) $next_page = true;
// build url components
if( !$next_page ) {
// 1st time
$search_res = 'BaseSearchResource'; // end point
$path = '&module_path=' . urlencode('SearchInfoBar(query=' . $search_str . ', scope=boards)');
$data = preg_replace("'[\n\r\s\t]'","",'{
"options":{
"scope":"pins",
"show_scope_selector":true,
"query":"' . $search_str . '"
},
"context":{
"app_version":"2f83a7e"
},
"module":{
"name":"SearchPage",
"options":{
"scope":"pins",
"query":"' . $search_str . '"
}
},
"append":false,
"error_strategy":0
}');
} else {
// this is a fetch for 'scrolling', what changes is the bookmarks reference,
// so pass the previous bookmarks value to this function and it is included
// in query
$search_res = 'SearchResource'; // different end point from 1st time search
$path = '';
$data = preg_replace("'[\n\r\s\t]'","",'{
"options":{
"query":"' . $search_str . '",
"bookmarks":["' . $bookmarks_str . '"],
"show_scope_selector":null,
"scope":"pins"
},
"context":{
"app_version":"2f83a7e"
},
"module":{
"name":"GridItems",
"options":{
"scrollable":true,
"show_grid_footer":true,
"centered":true,
"reflow_all":true,
"virtualize":true,
"item_options":{
"show_pinner":true,
"show_pinned_from":false,
"show_board":true
},
"layout":"variable_height"
}
},
"append":true,
"error_strategy":2
}');
}
$data = urlencode($data);
$timestamp = time() * 1000; // unix time but in JS format (ie. has ms vs normal server time in secs), * 1000 to add ms (ie. 0ms)
// build url
$url = 'http://pinterest.com/resource/' . $search_res . '/get/?source_url=/search/pins/?q=' . $search_str
. '&data=' . $data
. $path
. '&_=' . $timestamp;//'1378150472669';
// setup curl
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array("X-Requested-With: XMLHttpRequest"));
// get result
$curl_result = curl_exec ($ch); // this echoes the output
$curl_result = json_decode($curl_result);
curl_close ($ch);
// clear html to make var_dumps easier to see when debugging
// $curl_result->module->html = '';
// isolate the pin data, different end points have different data structures
if(!$next_page) $pin_array = $curl_result->module->tree->children[1]->children[0]->children[0]->children;
else $pin_array = $curl_result->module->tree->children;
// map the pin data into desired format
$pin_data_array = array();
$bookmarks = null;
if(is_array($pin_array)) {
if(count($pin_array)) {
foreach ($pin_array as $pin) {
//setup data
$image_id = $pin->options->pin_id;
$image_data = ( isset($pin->data->images->originals) ) ? $pin->data->images->originals : $pin->data->images->orig;
$pin_url = 'http://pinterest.com/pin/' . $image_id . '/';
$original_url = $pin->data->link;
$video = $pin->data->is_video;
array_push($pin_data_array, array(
"id" => $image_id,
"image" => $image_data,
"pin_link" => $pin_url,
"orig_link" => $original_url,
"video_flag" => $video,
));
}
$bookmarks = reset($curl_result->module->tree->resource->options->bookmarks);
} else {
$pin_data_array = false;
}
}
// recurse until we're done
if( !($pin_data_array === false) && !is_null($bookmarks) ) {
// more pins to get
$more_pins = $this->get_tagged_pins($search_str, $limit, $bookmarks, ++$page);
if( !($more_pins === false) ) $pin_data_array = array_merge($pin_data_array, $more_pins);
return $pin_data_array;
}
// end of recursion
return false;
}
} // end class Skrivener_Pins
} // end if
/**
* Debug/Demo Code
* delete or comment this section for production
*/
// output headers to control how the content displays
// header("Content-Type: application/json");
header("Content-Type: text/plain");
// header("Content-Type: text/html");
// define search term
// $tag = "vader";
$tag = "haemolytic";
// $tag = "qjkjgjerbjjkrekhjk";
if(class_exists('Skrivener_Pins')) {
// instantiate the class
$pin_handler = new Skrivener_Pins();
// get pins, pinterest returns 25 per batch, function pages through this recursively, pass in limit to
// override default limit on number of pages to retrieve, avoid high limits (eg. limit of 20 * 25 pins/page = 500 pins to pull
// and 20 separate calls to Pinterest)
$pins1 = $pin_handler->get_tagged_pins($tag, 2);
// display the pins for demo purposes
echo '<h1>Images on Pinterest mentioning "' . $tag . '"</h1>' . "\n";
if( $pins1 != false ) {
echo '<p><em>' . count($pins1) . ' images found.</em></p>' . "\n";
skrivener_dump_images($pins1, 5);
} else {
echo '<p><em>No images found.</em></p>' . "\n";
}
}
// demo function, dumps images in array to html img tags, can pass limit to only display part of array
function skrivener_dump_images($pin_array, $limit = false) {
if(is_array($pin_array)) {
if($limit) $pin_array = array_slice($pin_array, -($limit));
foreach ($pin_array as $pin) {
echo '<img src="' . $pin['image']->url . '" width="' . $pin['image']->width . '" height="' . $pin['image']->height . '" >' . "\n";
}
}
}
?>
如果您遇到问题,请根据您的特定终点让我知道。对于代码中任何邋iness的Apols,它最初都没有生产。
答案 1 :(得分:1)
有几个人建议使用javascript来模拟滚动。
我认为你根本不需要模拟滚动,我认为只需要在滚动发生时找出通过AJAX调用的URI的格式,然后你就可以按顺序获得结果的每个“页面”。需要一点落后的工程。
使用Chrome检查器的网络标签我可以看到,一旦我到达页面的某个距离,就会调用此URI:
如果我们解码它,我们发现它主要是JSON
http://pinterest.com/resource/BoardFeedResource/get/?source_url=/dodo/web-designui-and-mobile/&data=
{
"options": {
"board_id": "158400180582875562",
"access": [],
"bookmarks": [
"LT4xNTg0MDAxMTE4NjcxMTM2ODk6MjV8ZWJjODJjOWI4NTQ4NjU4ZDMyNzhmN2U3MGQyZGJhYTJhZjY2ODUzNTI4YTZhY2NlNmY0M2I1ODYwYjExZmQ3Yw=="
]
},
"context": {
"app_version": "fb43cdb"
},
"module": {
"name": "GridItems",
"options": {
"scrollable": true,
"show_grid_footer": true,
"centered": true,
"reflow_all": true,
"virtualize": true,
"item_options": {
"show_rich_title": false,
"squish_giraffe_pins": false,
"show_board": false,
"show_via": false,
"show_pinner": false,
"show_pinned_from": true
},
"layout": "variable_height"
}
},
"append": true,
"error_strategy": 1
}
&_=1377091719636
向下滚动,直到我们收到第二个请求,然后我们会看到
http://pinterest.com/resource/BoardFeedResource/get/?source_url=/dodo/web-designui-and-mobile/&data=
{
"options": {
"board_id": "158400180582875562",
"access": [],
"bookmarks": [
"LT4xNTg0MDAxMTE4NjcwNTk1ODQ6NDl8ODFlMDUwYzVlYWQxNzVmYzdkMzI0YTJiOWJkYzUwOWFhZGFkM2M1MzhiNzA0ZDliZDIzYzE3NjkzNTg1ZTEyOQ=="
]
},
"context": {
"app_version": "fb43cdb"
},
"module": {
"name": "GridItems",
"options": {
"scrollable": true,
"show_grid_footer": true,
"centered": true,
"reflow_all": true,
"virtualize": true,
"item_options": {
"show_rich_title": false,
"squish_giraffe_pins": false,
"show_board": false,
"show_via": false,
"show_pinner": false,
"show_pinned_from": true
},
"layout": "variable_height"
}
},
"append": true,
"error_strategy": 2
}
&_=1377092231234
正如您所看到的,没有太大变化。 Board_id是一样的。 error_strategy现在是2,而最后的&amp; _是不同的。
&amp; _参数在这里是关键。我敢打赌,它告诉页面从哪里开始下一组照片。我无法在任何响应或原始页面HTML中找到它的引用,但它必须在某处,或者在客户端通过javascript生成。无论哪种方式,页面/浏览器都必须知道下一步要求什么,所以这些信息是你应该能够获得的。
答案 2 :(得分:1)
可能有点晚了,但是,使用py3-pinterest开源项目,您可以轻松地做到这一点:
首先从板上获取所有引脚作为对象,它们还包括原始图像URL。
# get all pins for the board
board_pins = []
pin_batch = pinterest.board_feed(board_id=target_board['id'], board_url=target_board['url'])
while len(pin_batch) > 0:
board_pins += pin_batch
pin_batch = pinterest.board_feed(board_id=target_board['id'], board_url=target_board['url'])
然后,您可以获取图像URL并下载它们或对它们进行任何操作
for pin in board_pins:
url = pin['image']
# process image url..
完整代码示例: https://github.com/bstoilov/py3-pinterest/blob/master/download_board_images.py
是的,但是您仍然坚持使用c#吗?应该很容易将其移植:)
答案 3 :(得分:0)
您可以通过使用此标头发出请求来触发json端点:X-Requested-With:XMLHttpRequest
在控制台中尝试使用此命令:
curl -H "X-Requested-With:XMLHttpRequest" "http://pinterest.com/resource/CategoryFeedResource/get/?source_url=%2Fall%2Fgeek%2F&data=%7B%22options%22%3A%7B%22feed%22%3A%22geek%22%2C%22scope%22%3Anull%2C%22bookmarks%22%3A%5B%22Pz8xMzc3NjU4MjEyLjc0Xy0xfDE1ZjczYzc4YzNlNDg3M2YyNDQ4NGU1ZTczMmM0ZTQyYzBjMWFiMWNhYjRhMDRhYjg2MTYwMGVkNWQ0ZDg1MTY%3D%22%5D%2C%22is_category_feed%22%3Atrue%7D%2C%22context%22%3A%7B%22app_version%22%3A%22addc92b%22%7D%2C%22module%22%3A%7B%22name%22%3A%22GridItems%22%2C%22options%22%3A%7B%22scrollable%22%3Atrue%2C%22show_grid_footer%22%3Atrue%2C%22centered%22%3Atrue%2C%22reflow_all%22%3Atrue%2C%22virtualize%22%3Atrue%2C%22item_options%22%3A%7B%22show_pinner%22%3Atrue%2C%22show_pinned_from%22%3Afalse%2C%22show_board%22%3Atrue%2C%22show_via%22%3Afalse%7D%2C%22layout%22%3A%22variable_height%22%7D%7D%2C%22append%22%3Atrue%2C%22error_strategy%22%3A2%7D&module_path=App()%3EHeader()%3EDropdownButton()%3EDropdown()%3ECategoriesMenu(resource%3D%5Bobject+Object%5D%2C+name%3DCategoriesMenu%2C+resource%3DCategoriesResource(browsable%3Dtrue))&_=1377658213300" | python -mjson.tool
您将在输出的json中看到引脚数据。您应该能够解析它并获取您需要的下一个图像。
至于这一点:&_=1377658213300
。我推测这是前一个列表的最后一个引脚的id。您应该可以使用上一个响应中的最后一个引脚在每次调用时替换它。
答案 4 :(得分:0)
#!/usr/bin/env bash
##
## File: getpins.bsh
##
## Copyrighted by +A.M.Danischewski 2016+ (c)
## This program may be reutilized without limits, provided this
## notice remain intact.
## If this breaks one day, then just fire up firefox Developer Tools and check the network traffic to
## capture "copy as curl" of the calls to the search page (filter with BaseSearchResource), then the
## call to feed more data (filter with SearchResource).
##
## Do a search on whatever you want remove the cookie header, and add -o ret2.html -D h2.txt -c c1.txt,
## then search replace the search terms as SEARCHTOKEN1 and SEARCHTOKEN2.
##
## Description this script facilitates alternate browsers, by caching images/pins
## from pinterest. This script is hardwired for two search terms. First create a directory
## to where you want the images to go, then cd there.
## Usage:
## $> cd /big/drive/auto_gyros
## $> getpins.bsh "sleek autogyros"
##
## Expect around 900 images to land wherever you select, so make sure you have space! =)
##
declare -r ORIG_IMGS="pin_orig_imgs.txt"
declare -r TMP_IMGS="pin_imgs.txt"
declare -r UA_HEADER="User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:19.$(($RANDOM%10))) Gecko/20100101 Firefox/19.0"
## Say Hello to the main page and get a cookie.
declare PINCMD1=$(cat << EOF
curl -o ret1.html -D h1.txt -c c1.txt -H 'Host: www.pinterest.com' -H '${UA_HEADER}' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' -H 'Accept-Language: en-US,en;q=0.5' --compressed -H 'Connection: keep-alive' 'https://www.pinterest.com/'
EOF
)
## Start a search for our dear search terms.
declare PINCMD2=$(cat << EOF
curl -H 'X-APP-VERSION: ea7a93a' -o ret2.html -D h2.txt -c c1.txt -H 'Host: www.pinterest.com' -H '${UA_HEADER}' -H 'Accept: application/json, text/javascript, */*; q=0.01' -H 'Accept-Language: en-US,en;q=0.5' --compressed -H 'X-Pinterest-AppState: active' -H 'X-NEW-APP: 1' -H 'X-Requested-With: XMLHttpRequest' -H 'Referer: https://www.pinterest.com' -H 'Connection: keep-alive' 'https://www.pinterest.com/resource/BaseSearchResource/get/?source_url=%2Fsearch%2Fpins%2F%3Fq%3DSEARCHTOKEN1%2520SEARCHTOKEN2%26rs%3Dtyped%260%3DSEARCHTOKEN1%257Ctyped%261%3DSEARCHTOKEN2%257Ctyped&data=%7B%22options%22%3A%7B%22restrict%22%3Anull%2C%22scope%22%3A%22pins%22%2C%22constraint_string%22%3Anull%2C%22show_scope_selector%22%3Atrue%2C%22query%22%3A%22SEARCHTOKEN1+SEARCHTOKEN2%22%7D%2C%22context%22%3A%7B%7D%2C%22module%22%3A%7B%22name%22%3A%22SearchPage%22%2C%22options%22%3A%7B%22restrict%22%3Anull%2C%22scope%22%3A%22pins%22%2C%22constraint_string%22%3Anull%2C%22show_scope_selector%22%3Atrue%2C%22query%22%3A%22SEARCHTOKEN1+SEARCHTOKEN2%22%7D%7D%2C%22render_type%22%3A1%2C%22error_strategy%22%3A0%7D&module_path=App%3EHeader%3ESearchForm%3ETypeaheadField(support_guided_search%3Dtrue%2C+resource_name%3DAdvancedTypeaheadResource%2C+tags%3Dautocomplete%2C+class_name%3DbuttonOnRight%2C+prefetch_on_focus%3Dtrue%2C+support_advanced_typeahead%3Dnull%2C+hide_tokens_on_focus%3Dundefined%2C+search_on_focus%3Dtrue%2C+placeholder%3DSearch%2C+show_remove_all%3Dtrue%2C+enable_recent_queries%3Dtrue%2C+name%3Dq%2C+view_type%3Dguided%2C+value%3D%22%22%2C+input_log_element_type%3D227%2C+populate_on_result_highlight%3Dtrue%2C+search_delay%3D0%2C+is_multiobject_search%3Dtrue%2C+type%3Dtokenized%2C+enable_overlay%3Dtrue)&_=1454779874891'
EOF
)
## Load further images.
declare PINCMD3=$(cat << EOF
curl -H 'X-APP-VERSION: ea7a93a' -D h3.txt -c c1.txt -H 'Host: www.pinterest.com' -H '${UA_HEADER}' -H 'Accept: application/json, text/javascript, */*; q=0.01' -H 'Accept-Language: en-US,en;q=0.5' --compressed -H 'X-Pinterest-AppState: active' -H 'X-NEW-APP: 1' -H 'X-Requested-With: XMLHttpRequest' -H 'Referer: https://www.pinterest.com' -H 'Connection: keep-alive' 'https://www.pinterest.com/resource/SearchResource/get/?source_url=%2Fsearch%2Fpins%2F%3Fq%3DSEARCHTOKEN1%2520SEARCHTOKEN2%26rs%3Dtyped%260%3DSEARCHTOKEN1%257Ctyped%261%3DSEARCHTOKEN2%257Ctyped&data=%7B%22options%22%3A%7B%22layout%22%3Anull%2C%22places%22%3Afalse%2C%22constraint_string%22%3Anull%2C%22show_scope_selector%22%3Atrue%2C%22query%22%3A%22SEARCHTOKEN1+SEARCHTOKEN2%22%2C%22scope%22%3A%22pins%22%2C%22bookmarks%22%3A%5B%22_NEW_BOOK_MARK_%22%5D%7D%2C%22context%22%3A%7B%7D%7D&module_path=App%3EHeader%3ESearchForm%3ETypeaheadField(support_guided_search%3Dtrue%2C+resource_name%3DAdvancedTypeaheadResource%2C+tags%3Dautocomplete%2C+class_name%3DbuttonOnRight%2C+prefetch_on_focus%3Dtrue%2C+support_advanced_typeahead%3Dnull%2C+hide_tokens_on_focus%3Dundefined%2C+search_on_focus%3Dtrue%2C+placeholder%3DSearch%2C+show_remove_all%3Dtrue%2C+enable_recent_queries%3Dtrue%2C+name%3Dq%2C+view_type%3Dguided%2C+value%3D%22%22%2C+input_log_element_type%3D227%2C+populate_on_result_highlight%3Dtrue%2C+search_delay%3D0%2C+is_multiobject_search%3Dtrue%2C+type%3Dtokenized%2C+enable_overlay%3Dtrue)&_=1454779874911'
EOF
)
## Exactly 2 search terms in a single string are expected, you can hack it up if
## you want something else.
declare SEARCHTOKEN1=$(echo "${1}" | cut -d " " -f1)
declare SEARCHTOKEN2=$(echo "${1}" | cut -d " " -f2)
PINCMD3=$(sed "s/SEARCHTOKEN1/${SEARCHTOKEN1}/g" <<< "${PINCMD3}")
PINCMD3=$(sed "s/SEARCHTOKEN2/${SEARCHTOKEN2}/g" <<< "${PINCMD3}")
PINCMD2=$(sed "s/SEARCHTOKEN1/${SEARCHTOKEN1}/g" <<< "${PINCMD2}")
PINCMD2=$(sed "s/SEARCHTOKEN2/${SEARCHTOKEN2}/g" <<< "${PINCMD2}")
function lspinimgs() { grep -o "\"url\": \"http[s]*://[^\"]*.pinimg.com[^\"]*.jpg\"" "${1}" | cut -d " " -f2 | tr -d "\""; }
function mkpinorig() { sed "s#\(^http.*\)\(com/\)\([^/]*\)\(/.*jpg\$\)#\1\2originals\4#g" "${1}" > "${2}"; }
function getpinbm() { grep -o "bookmarks\": [^ ]* " "${1}" | sed "s/^book.*\[\"//g;s/\"\].*\$//g" | sort | uniq | grep -v "-end-"; }
function changepinbm() { PINCMD3=$(sed "s/\(^.*\)\(bookmarks%22%3A%5B%22\)\(.*\)\(%22%5D.*\$\)/\1\2${1}\4/g" <<< "${PINCMD3}"); }
function cleanup() { rm ret*html c1.txt "${TMP_IMGS}" h{1..3}.txt "${ORIG_IMGS}"; }
function main() {
eval "${PINCMD1}"
eval "${PINCMD2}"
for ((i=3,lasti=2; i<10000; i++,lasti++)); do
pinbm=$(getpinbm "ret${lasti}.html")
[[ -z "${pinbm}" ]] && break
changepinbm "${pinbm}"
eval "${PINCMD3}" > "ret${i}.html"
done
for a in *.html; do lspinimgs "${a}" >> "${TMP_IMGS}"; done
mkpinorig "${TMP_IMGS}" "${ORIG_IMGS}"
IFS=$(echo -en "\n\b") && for a in $(sort "${ORIG_IMGS}" | uniq); do
wget --tries=3 -E -e robots=off -nc --random-wait --content-disposition --no-check-certificate -p --restrict-file-names=windows,lowercase,ascii --header "${UA_HEADER}" -nd "$a"
done
cleanup
}
main
exit 0