backend = new backend("baidu"); include "lib/fuckhtml.php"; $this->fuckhtml = new fuckhtml(); $this->proc = curl_multi_init(); $this->handles = []; $this->cookie = null; } public function getfilters($page){ switch($page){ case "web": return [ "newer" => [ "display" => "Newer than", "option" => "_DATE" ], "older" => [ "display" => "Older than", "option" => "_DATE" ] ]; break; case "images": return [ "sort" => [ "display" => "Sort", "option" => [ "relevance" => "Relevance", // no param "latest" => "Latest", // &latest=1 "hot" => "Hot" // &hot=1 ] ], "size" => [ "display" => "Size", "option" => [ "any" => "Any size", "7" => "Extra large (1080px+)", // &z=7 "6" => "Large (600px~1080px)", // &z=6 "5" => "Medium (300px~600px)", // &z=5 "4" => "Small (1px~300px)" // &z=4 ] ], "ratio" => [ "display" => "Ratio", "option" => [ "any" => "Any ratio", "1" => "Tall vertical", // &imgratio=1 "2" => "Vertical", // &imgratio=2 "3" => "Square", // &imgratio=3 "4" => "Horizontal", // &imgratio=4 "5" => "Wide horizontal" // &imgratio=5 ] ], "format" => [ "display" => "Format", "option" => [ "any" => "Any format", "3" => "JPG", // &imgformat=3 "5" => "JPEG", // &imgformat=5 "4" => "PNG", // &imgformat=4 "2" => "BMP", // &imgformat=2 "6" => "GIF (Animated)" // &imgformat=6 ] ], "color" => [ "display" => "Color", "option" => [ "any" => "Any color", "1024" => "White", // &ic=1024 "2048" => "Black & White", "512" => "Black", "64" => "Magenta", "16" => "Blue", "1" => "Red", "2" => "Yellow", "32" => "Purple", "4" => "Green", "8" => "Teal", "256" => "Orange", "128" => "Brown" ] ], "type" => [ "display" => "Type", "option" => [ "any" => "Any type", "hd" => "HD", // &hd=1 "isImgSet" => "Photo album", // &isImgSet=1 "copyright" => "Copyright" // ©right=1 ] ] ]; break; case "videos": return []; break; case "news": return [ "category" => [ "display" => "Category", "option" => [ "any" => "All news", "media" => "Media websites", // &medium=1 "baijiahao" => "Baidu Baijiahao" // &medium=2 ] ] ]; break; } } private function get($proxy, $url, $get = [], $referer = false){ $curlproc = curl_init(); if($get !== []){ $get = http_build_query($get); $url .= "?" . $get; } $cookies_tmp = []; curl_setopt($curlproc, CURLOPT_HEADERFUNCTION, function($curlproc, $header) use (&$cookies_tmp){ $length = strlen($header); $header = explode(":", $header, 2); if(trim(strtolower($header[0])) == "set-cookie"){ $cookie_tmp = explode("=", trim($header[1]), 2); $cookies_tmp[trim($cookie_tmp[0])] = explode(";", $cookie_tmp[1], 2)[0]; } return $length; }); curl_setopt($curlproc, CURLOPT_URL, $url); curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding if($referer === false){ if($this->cookie === null){ curl_setopt($curlproc, CURLOPT_HTTPHEADER, ["User-Agent: " . config::USER_AGENT, "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language: en-US,en;q=0.5", "Accept-Encoding: gzip, deflate, br, zstd", "DNT: 1", "Sec-GPC: 1", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "Sec-Fetch-Dest: document", "Sec-Fetch-Mode: navigate", "Sec-Fetch-Site: cross-site", "Priority: u=0, i"] ); }else{ curl_setopt($curlproc, CURLOPT_HTTPHEADER, ["User-Agent: " . config::USER_AGENT, "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language: en-US,en;q=0.5", "Accept-Encoding: gzip, deflate, br, zstd", "DNT: 1", "Sec-GPC: 1", "Connection: keep-alive", "Cookie: {$this->cookie}", "Upgrade-Insecure-Requests: 1", "Sec-Fetch-Dest: document", "Sec-Fetch-Mode: navigate", "Sec-Fetch-Site: cross-site", "Priority: u=0, i"] ); } }else{ if($this->cookie === null){ curl_setopt($curlproc, CURLOPT_HTTPHEADER, ["User-Agent: " . config::USER_AGENT, "Accept: application/json, text/plain, */*", "Accept-Language: en-US,en;q=0.5", "Accept-Encoding: gzip, deflate, br, zstd", "Referer: {$referer}", "DNT: 1", "Sec-GPC: 1", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "Sec-Fetch-Dest: empty", "Sec-Fetch-Mode: cors", "Sec-Fetch-Site: same-origin"] ); }else{ curl_setopt($curlproc, CURLOPT_HTTPHEADER, ["User-Agent: " . config::USER_AGENT, "Accept: application/json, text/plain, */*", "Accept-Language: en-US,en;q=0.5", "Accept-Encoding: gzip, deflate, br, zstd", "Referer: {$referer}", "DNT: 1", "Sec-GPC: 1", "Connection: keep-alive", "Cookie: {$this->cookie}", "Upgrade-Insecure-Requests: 1", "Sec-Fetch-Dest: empty", "Sec-Fetch-Mode: cors", "Sec-Fetch-Site: same-origin"] ); } } curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true); curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2); curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30); curl_setopt($curlproc, CURLOPT_TIMEOUT, 30); $this->backend->assign_proxy($curlproc, $proxy); $data = curl_exec($curlproc); if(curl_errno($curlproc)){ throw new Exception(curl_error($curlproc)); } // store cookie if(strlen($this->cookie) !== 0){ $this->cookie .= "; "; } foreach($cookies_tmp as $cookie_name => $cookie_value){ $this->cookie .= $cookie_name . "=" . $cookie_value . "; "; } $this->cookie = rtrim($this->cookie, " ;"); curl_close($curlproc); return $data; } private function redirect_add_url($proxy, $url){ if( preg_match( '/^https?:\/\/(?:www\.|m\.)?baidu\.com\/(?:link\?|from=0\/)/', $url ) === 0 ){ // not a baidu redirect return $url; } $curlproc = curl_init(); curl_setopt($curlproc, CURLOPT_URL, $url); curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding curl_setopt($curlproc, CURLOPT_HTTPHEADER, ["User-Agent: " . config::USER_AGENT, "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language: en-US,en;q=0.5", "Accept-Encoding: gzip, deflate, br, zstd", "DNT: 1", "Sec-GPC: 1", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "Sec-Fetch-Dest: document", "Sec-Fetch-Mode: navigate", "Sec-Fetch-Site: none", "Sec-Fetch-User: ?1", "Priority: u=0, i"] ); curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true); curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2); curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30); curl_setopt($curlproc, CURLOPT_TIMEOUT, 30); //curl_setopt($curlproc, CURLOPT_HEADER, true); //curl_setopt($curlproc, CURLOPT_NOBODY, true); $this->backend->assign_proxy($curlproc, $proxy); curl_multi_add_handle($this->proc, $curlproc); $this->handles[] = [ "replacement" => $url, "handle" => $curlproc ]; return $url; } private function resolve_urls(&$scraped_data){ do{ do{ $status = curl_multi_exec($this->proc, $active); }while($status === CURLM_CALL_MULTI_PERFORM); if($active){ curl_multi_select($this->proc); } }while($active && $status === CURLM_OK); // // if we reach this, we're done downloading garbage // // parse redirect values & create data shit $keys = []; $replacements = []; for($i=0; $ihandles); $i++){ preg_match( '/window\.location\.replace\("([^"]+)"\)/', curl_multi_getcontent($this->handles[$i]["handle"]), $location ); if( preg_match( '/^(https?:|)\/\//', $location[1] ) ){ $keys[] = $this->handles[$i]["replacement"]; $replacements[] = $location[1]; } } $this->recursive_replace($keys, $replacements, $scraped_data); curl_multi_close($this->proc); } private function recursive_replace(&$keys, &$replacements, &$pointer){ foreach($pointer as $key => &$value){ if($key == "url"){ $search = array_search( $value, $keys ); if($search !== false){ // replace url $value = $replacements[$search]; } }elseif(is_array($value)){ $this->recursive_replace($keys, $replacements, $value); } } } public function web($get){ if($get["npt"]){ [$json, $proxy] = $this->backend->get($get["npt"], "web"); $json = json_decode($json, true); $this->cookie = $json["cookie"]; $target = $json["target"]; try{ $html = $this->get( $proxy, $target, [] ); }catch(Exception $error){ throw new Exception("Failed to fetch search page"); } }else{ // // Get authentication token // $proxy = $this->backend->get_ip(); // running this will give us shit in $this->cookie // @TODO probably not needed? I get blocked anyways ffs //$this->get($proxy, "https://www.baidu.com", []); $npt_data = [ "word" => $get["s"] ]; // &gpc=stf%3D0%2C1752638400|stftype%3D2 if( $get["older"] !== false || $get["newer"] !== false ){ if($get["older"] === false){ $get["older"] = 0; } $npt_data["gpc"] = "stf={$get["older"]},{$get["newer"]}|stftype=2"; } try{ $html = $this->get( $proxy, "https://m.baidu.com/s", $npt_data ); }catch(Exception $error){ throw new Exception("Failed to fetch search page"); } //$html = file_get_contents("scraper/baidu.html"); } return $this->parse_search($proxy, "web", $html); } private function parse_search($proxy, $pagetype, $html){ // hack, maybe not needed anymore $html = str_replace(["\n", "\r"], "", $html); $out = [ "status" => "ok", "spelling" => [ "type" => "no_correction", "using" => null, "correction" => null ], "npt" => null, "answer" => [], "web" => [], "image" => [], "video" => [], "news" => [], "related" => [] ]; $this->fuckhtml->load($html); $this->detect_ass(); // get next page $npt = $this->fuckhtml ->getElementsByClassName( "new-nextpage-only", "a" ); if(count($npt) === 0){ $npt = $this->fuckhtml ->getElementsByClassName( "new-nextpage", "a" ); } if(count($npt) !== 0){ $out["npt"] = $this->backend->store( json_encode([ "cookie" => $this->cookie, "target" => $this->fuckhtml ->getTextContent( $npt[0]["attributes"]["href"] ) ]), "web", $proxy ); } // get "articles" $articles = $this->fuckhtml ->getElementsByTagName( "article" ); foreach($articles as $article){ //print_r($article); $this->fuckhtml->load($article); if( !isset($article["attributes"]["rl-link-data-ivk"]) && !isset($article["attributes"]["data-click-info"]) ){ // probe for related searches $relateds = $this->fuckhtml ->getElementsByClassName( "sc-sublink", "button" ); foreach($relateds as $related){ $out["related"][] = $this->fuckhtml ->getTextContent( $related ); } continue; } // find json object $jsons = []; if(isset($article["attributes"]["rl-link-data-ivk"])){ $jsons[] = &$article["attributes"]["rl-link-data-ivk"]; } if(isset($article["attributes"]["data-click-info"])){ $jsons[] = &$article["attributes"]["data-click-info"]; } if(isset($article["attributes"]["rl-link-data-log"])){ $jsons[] = &$article["attributes"]["rl-link-data-log"]; } // fail if(count($jsons) === 0){ continue; } $url = null; foreach($jsons as $json){ $json = json_decode( $this->fuckhtml ->getTextContent( $json ), true ); $url = null; if(isset($json["control"]["dataUrl"])){ $url = $json["control"]["dataUrl"]; break; }elseif(isset($json["control"]["default_url"])){ $url = $json["control"]["default_url"]; break; }elseif(isset($json["extra"])){ // decode extra object $json2 = json_decode($json["extra"], true); if(isset($json2["url"])){ // double chinese encryption $url = rawurldecode(rawurldecode($json2["url"])); break; } if(isset($json2["loc"])){ $url = rawurldecode(rawurldecode($json2["loc"])); break; } } } // fail if($url === null){ if(isset($article["attributes"]["rl-link-href"])){ $url = $this->fuckhtml ->getTextContent( $article["attributes"]["rl-link-href"] ); }else{ // horrible fail continue; } } // get title $title = $this->fuckhtml ->getElementsByAttributeValue( "role", "title", "div" ); if(count($title) === 0){ $title = $this->fuckhtml ->getElementsByClassName( "cosc-title-slot", "span" ); } if(count($title) === 0){ $title = $this->fuckhtml ->getElementsByClassName( "cu-title", "p" ); } if(count($title) !== 0){ $title = $this->titledots( $this->fuckhtml ->getTextContent( $title[0] ) ); }else{ $title = "Untitled"; } // get description $description = $this->fuckhtml ->getElementsByAttributeValue( "data-module", "abstract", "div" ); if(count($description) === 0){ $description = $this->fuckhtml ->getElementsByAttributeValue( "role", "text", "div" ); } if(count($description) === 0){ $description = $this->fuckhtml ->getElementsByClassName( "cos-gutter", "div" ); } if(count($description) !== 0){ $description = $this->titledots( $this->fuckhtml ->getTextContent( $description[0] ) ); }else{ $description = null; } // get date $date = $this->fuckhtml ->getElementsByClassName( "cosc-source-caption", "span" ); if(count($date) !== 0){ $date = $this->parse_time( $this->fuckhtml ->getTextContent( $date[0] ) ); }else{ $date = null; } // get thumb $image = $this->fuckhtml ->getElementsByClassName( "cos-image-body", "img" ); if(count($image) !== 0){ $thumb = [ "ratio" => "16:9", "url" => $this->fuckhtml ->getTextContent( $image[0]["attributes"]["src"] ) ]; }else{ $thumb = [ "ratio" => null, "url" => null ]; } $out["web"][] = [ "title" => $title, "description" => $description, "url" => $this->redirect_add_url($proxy, $url), "date" => $date, "type" => "web", "thumb" => $thumb, "sublink" => [], "table" => [] ]; } // dedup related searches $out["related"] = array_values(array_unique($out["related"])); $this->resolve_urls($out); return $out; } public function image($get){ // https://image.baidu.com/search/acjson?word=asmr&rn=60&pn=0&newReq=1 //$json = file_get_contents("scraper/baidu_img.json"); if($get["npt"]){ [$params, $proxy] = $this->backend->get($get["npt"], "images"); $params = json_decode($params, true); $params["pn"] = $params["pn"] + 60; }else{ $proxy = $this->backend->get_ip(); $params = [ "word" => $get["s"], "rn" => 60, // results/page "pn" => 0, // item increment (0 * 60) "newReq" => 1 // otherwise json is fucked up ]; switch($get["sort"]){ case "latest": $params["latest"] = 1; break; case "hot": $params["hot"] = 1; break; } if($get["size"] != "any"){ $params["z"] = $get["size"]; } if($get["ratio"] != "any"){ $params["imgratio"] = $get["ratio"]; } if($get["format"] != "any"){ $params["imgformat"] = $get["format"]; } if($get["color"] != "any"){ $params["ic"] = $get["color"]; } switch($get["type"]){ case "hd": $params["hd"] = 1; break; case "isImgSet": $params["isImgSet"] = 1; break; case "copyright": $params["copyright"] = 1; break; } } try{ $json = $this->get( $proxy, "https://image.baidu.com/search/acjson", $params, "https://image.baidu.com/search/index?tn=baiduimage&word=" . urlencode($get["s"]) ); }catch(Exception $error){ throw new Exception("Failed to fetch JSON"); } $json = json_decode($json, true); if($json === null){ // detect captcha first $this->fuckhtml->load($json); $this->detect_ass(); // fallback to json decode error throw new Exception("Failed to decode JSON"); } if( isset($json["message"]) && $json["message"] != "success" ){ throw new Exception("Baidu returned an error: {$json["message"]}"); } if(!isset($json["data"]["images"])){ throw new Exception("Baidu did not return an image object"); } $out = [ "status" => "ok", "npt" => null, "image" => [] ]; foreach($json["data"]["images"] as $image){ parse_str(parse_url($image["thumburl"], PHP_URL_QUERY), $thumb_size); $out["image"][] = [ "title" => $this->fuckhtml ->getTextContent( $image["titleShow"] ), "source" => [ [ "url" => $image["objurl"], "width" => (int)$image["width"], "height" => (int)$image["height"] ], [ // thumbnail "url" => $image["thumburl"], "width" => (int)$thumb_size["w"], "height" => (int)$thumb_size["h"] ] ], "url" => $image["fromUrl"] ]; } // // Detect if there's a next page // if((int)$json["data"]["totalNum"] >= $params["pn"] + 60){ $out["npt"] = $this->backend->store( json_encode($params), "images", $proxy ); } return $out; } public function video($get){ // https://www.baidu.com/sf/vsearch?pd=video&tn=vsearch&wd=jak%2Band%2Bdaxter&async=1&pn=0 // increase &pn +20 for pagination //$html = file_get_contents("scraper/baidu_vid.html"); if($get["npt"]){ [$params, $proxy] = $this->backend->get($get["npt"], "videos"); $params = json_decode($params, true); $params["pn"] = $params["pn"] + 10; }else{ $proxy = $this->backend->get_ip(); $params = [ "pd" => "video", "tn" => "vsearch", "wd" => $get["s"], "async" => 1, "pn" => 0 ]; } try{ $html = $this->get( $proxy, "https://www.baidu.com/sf/vsearch", $params ); }catch(Exception $error){ throw new Exception("Failed to get search page"); } $html = str_replace( ["\r", "\n"], "", $html ); $out = [ "status" => "ok", "npt" => null, "video" => [], "author" => [], "livestream" => [], "playlist" => [], "reel" => [] ]; $html = explode("