baidu.php 27 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460
  1. <?php
  2. class baidu{
  3. public function __construct(){
  4. include "lib/backend.php";
  5. $this->backend = new backend("baidu");
  6. include "lib/fuckhtml.php";
  7. $this->fuckhtml = new fuckhtml();
  8. $this->proc = curl_multi_init();
  9. $this->handles = [];
  10. $this->cookie = null;
  11. }
  12. public function getfilters($page){
  13. switch($page){
  14. case "web":
  15. return
  16. [
  17. "newer" => [
  18. "display" => "Newer than",
  19. "option" => "_DATE"
  20. ],
  21. "older" => [
  22. "display" => "Older than",
  23. "option" => "_DATE"
  24. ]
  25. ];
  26. break;
  27. case "images":
  28. return
  29. [
  30. "sort" => [
  31. "display" => "Sort",
  32. "option" => [
  33. "relevance" => "Relevance", // no param
  34. "latest" => "Latest", // &latest=1
  35. "hot" => "Hot" // &hot=1
  36. ]
  37. ],
  38. "size" => [
  39. "display" => "Size",
  40. "option" => [
  41. "any" => "Any size",
  42. "7" => "Extra large (1080px+)", // &z=7
  43. "6" => "Large (600px~1080px)", // &z=6
  44. "5" => "Medium (300px~600px)", // &z=5
  45. "4" => "Small (1px~300px)" // &z=4
  46. ]
  47. ],
  48. "ratio" => [
  49. "display" => "Ratio",
  50. "option" => [
  51. "any" => "Any ratio",
  52. "1" => "Tall vertical", // &imgratio=1
  53. "2" => "Vertical", // &imgratio=2
  54. "3" => "Square", // &imgratio=3
  55. "4" => "Horizontal", // &imgratio=4
  56. "5" => "Wide horizontal" // &imgratio=5
  57. ]
  58. ],
  59. "format" => [
  60. "display" => "Format",
  61. "option" => [
  62. "any" => "Any format",
  63. "3" => "JPG", // &imgformat=3
  64. "5" => "JPEG", // &imgformat=5
  65. "4" => "PNG", // &imgformat=4
  66. "2" => "BMP", // &imgformat=2
  67. "6" => "GIF (Animated)" // &imgformat=6
  68. ]
  69. ],
  70. "color" => [
  71. "display" => "Color",
  72. "option" => [
  73. "any" => "Any color",
  74. "1024" => "White", // &ic=1024
  75. "2048" => "Black & White",
  76. "512" => "Black",
  77. "64" => "Magenta",
  78. "16" => "Blue",
  79. "1" => "Red",
  80. "2" => "Yellow",
  81. "32" => "Purple",
  82. "4" => "Green",
  83. "8" => "Teal",
  84. "256" => "Orange",
  85. "128" => "Brown"
  86. ]
  87. ],
  88. "type" => [
  89. "display" => "Type",
  90. "option" => [
  91. "any" => "Any type",
  92. "hd" => "HD", // &hd=1
  93. "isImgSet" => "Photo album", // &isImgSet=1
  94. "copyright" => "Copyright" // &copyright=1
  95. ]
  96. ]
  97. ];
  98. break;
  99. case "videos":
  100. return [];
  101. break;
  102. case "news":
  103. return [
  104. "category" => [
  105. "display" => "Category",
  106. "option" => [
  107. "any" => "All news",
  108. "media" => "Media websites", // &medium=1
  109. "baijiahao" => "Baidu Baijiahao" // &medium=2
  110. ]
  111. ]
  112. ];
  113. break;
  114. }
  115. }
  116. private function get($proxy, $url, $get = [], $referer = false){
  117. $curlproc = curl_init();
  118. if($get !== []){
  119. $get = http_build_query($get);
  120. $url .= "?" . $get;
  121. }
  122. $cookies_tmp = [];
  123. curl_setopt($curlproc, CURLOPT_HEADERFUNCTION, function($curlproc, $header) use (&$cookies_tmp){
  124. $length = strlen($header);
  125. $header = explode(":", $header, 2);
  126. if(trim(strtolower($header[0])) == "set-cookie"){
  127. $cookie_tmp = explode("=", trim($header[1]), 2);
  128. $cookies_tmp[trim($cookie_tmp[0])] =
  129. explode(";", $cookie_tmp[1], 2)[0];
  130. }
  131. return $length;
  132. });
  133. curl_setopt($curlproc, CURLOPT_URL, $url);
  134. curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
  135. if($referer === false){
  136. if($this->cookie === null){
  137. curl_setopt($curlproc, CURLOPT_HTTPHEADER,
  138. ["User-Agent: " . config::USER_AGENT,
  139. "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
  140. "Accept-Language: en-US,en;q=0.5",
  141. "Accept-Encoding: gzip, deflate, br, zstd",
  142. "DNT: 1",
  143. "Sec-GPC: 1",
  144. "Connection: keep-alive",
  145. "Upgrade-Insecure-Requests: 1",
  146. "Sec-Fetch-Dest: document",
  147. "Sec-Fetch-Mode: navigate",
  148. "Sec-Fetch-Site: cross-site",
  149. "Priority: u=0, i"]
  150. );
  151. }else{
  152. curl_setopt($curlproc, CURLOPT_HTTPHEADER,
  153. ["User-Agent: " . config::USER_AGENT,
  154. "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
  155. "Accept-Language: en-US,en;q=0.5",
  156. "Accept-Encoding: gzip, deflate, br, zstd",
  157. "DNT: 1",
  158. "Sec-GPC: 1",
  159. "Connection: keep-alive",
  160. "Cookie: {$this->cookie}",
  161. "Upgrade-Insecure-Requests: 1",
  162. "Sec-Fetch-Dest: document",
  163. "Sec-Fetch-Mode: navigate",
  164. "Sec-Fetch-Site: cross-site",
  165. "Priority: u=0, i"]
  166. );
  167. }
  168. }else{
  169. if($this->cookie === null){
  170. curl_setopt($curlproc, CURLOPT_HTTPHEADER,
  171. ["User-Agent: " . config::USER_AGENT,
  172. "Accept: application/json, text/plain, */*",
  173. "Accept-Language: en-US,en;q=0.5",
  174. "Accept-Encoding: gzip, deflate, br, zstd",
  175. "Referer: {$referer}",
  176. "DNT: 1",
  177. "Sec-GPC: 1",
  178. "Connection: keep-alive",
  179. "Upgrade-Insecure-Requests: 1",
  180. "Sec-Fetch-Dest: empty",
  181. "Sec-Fetch-Mode: cors",
  182. "Sec-Fetch-Site: same-origin"]
  183. );
  184. }else{
  185. curl_setopt($curlproc, CURLOPT_HTTPHEADER,
  186. ["User-Agent: " . config::USER_AGENT,
  187. "Accept: application/json, text/plain, */*",
  188. "Accept-Language: en-US,en;q=0.5",
  189. "Accept-Encoding: gzip, deflate, br, zstd",
  190. "Referer: {$referer}",
  191. "DNT: 1",
  192. "Sec-GPC: 1",
  193. "Connection: keep-alive",
  194. "Cookie: {$this->cookie}",
  195. "Upgrade-Insecure-Requests: 1",
  196. "Sec-Fetch-Dest: empty",
  197. "Sec-Fetch-Mode: cors",
  198. "Sec-Fetch-Site: same-origin"]
  199. );
  200. }
  201. }
  202. curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
  203. curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
  204. curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
  205. curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
  206. curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
  207. $this->backend->assign_proxy($curlproc, $proxy);
  208. $data = curl_exec($curlproc);
  209. if(curl_errno($curlproc)){
  210. throw new Exception(curl_error($curlproc));
  211. }
  212. // store cookie
  213. if(strlen($this->cookie) !== 0){
  214. $this->cookie .= "; ";
  215. }
  216. foreach($cookies_tmp as $cookie_name => $cookie_value){
  217. $this->cookie .= $cookie_name . "=" . $cookie_value . "; ";
  218. }
  219. $this->cookie = rtrim($this->cookie, " ;");
  220. curl_close($curlproc);
  221. return $data;
  222. }
  223. private function redirect_add_url($proxy, $url){
  224. if(
  225. preg_match(
  226. '/^https?:\/\/(?:www\.|m\.)?baidu\.com\/(?:link\?|from=0\/)/',
  227. $url
  228. ) === 0
  229. ){
  230. // not a baidu redirect
  231. return $url;
  232. }
  233. $curlproc = curl_init();
  234. curl_setopt($curlproc, CURLOPT_URL, $url);
  235. curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
  236. curl_setopt($curlproc, CURLOPT_HTTPHEADER,
  237. ["User-Agent: " . config::USER_AGENT,
  238. "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
  239. "Accept-Language: en-US,en;q=0.5",
  240. "Accept-Encoding: gzip, deflate, br, zstd",
  241. "DNT: 1",
  242. "Sec-GPC: 1",
  243. "Connection: keep-alive",
  244. "Upgrade-Insecure-Requests: 1",
  245. "Sec-Fetch-Dest: document",
  246. "Sec-Fetch-Mode: navigate",
  247. "Sec-Fetch-Site: none",
  248. "Sec-Fetch-User: ?1",
  249. "Priority: u=0, i"]
  250. );
  251. curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
  252. curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
  253. curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
  254. curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
  255. curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
  256. //curl_setopt($curlproc, CURLOPT_HEADER, true);
  257. //curl_setopt($curlproc, CURLOPT_NOBODY, true);
  258. $this->backend->assign_proxy($curlproc, $proxy);
  259. curl_multi_add_handle($this->proc, $curlproc);
  260. $this->handles[] = [
  261. "replacement" => $url,
  262. "handle" => $curlproc
  263. ];
  264. return $url;
  265. }
  266. private function resolve_urls(&$scraped_data){
  267. do{
  268. do{
  269. $status = curl_multi_exec($this->proc, $active);
  270. }while($status === CURLM_CALL_MULTI_PERFORM);
  271. if($active){
  272. curl_multi_select($this->proc);
  273. }
  274. }while($active && $status === CURLM_OK);
  275. //
  276. // if we reach this, we're done downloading garbage
  277. //
  278. // parse redirect values & create data shit
  279. $keys = [];
  280. $replacements = [];
  281. for($i=0; $i<count($this->handles); $i++){
  282. preg_match(
  283. '/window\.location\.replace\("([^"]+)"\)/',
  284. curl_multi_getcontent($this->handles[$i]["handle"]),
  285. $location
  286. );
  287. if(
  288. preg_match(
  289. '/^(https?:|)\/\//',
  290. $location[1]
  291. )
  292. ){
  293. $keys[] = $this->handles[$i]["replacement"];
  294. $replacements[] = $location[1];
  295. }
  296. }
  297. $this->recursive_replace($keys, $replacements, $scraped_data);
  298. curl_multi_close($this->proc);
  299. }
  300. private function recursive_replace(&$keys, &$replacements, &$pointer){
  301. foreach($pointer as $key => &$value){
  302. if($key == "url"){
  303. $search =
  304. array_search(
  305. $value,
  306. $keys
  307. );
  308. if($search !== false){
  309. // replace url
  310. $value = $replacements[$search];
  311. }
  312. }elseif(is_array($value)){
  313. $this->recursive_replace($keys, $replacements, $value);
  314. }
  315. }
  316. }
  317. public function web($get){
  318. if($get["npt"]){
  319. [$json, $proxy] = $this->backend->get($get["npt"], "web");
  320. $json = json_decode($json, true);
  321. $this->cookie = $json["cookie"];
  322. $target = $json["target"];
  323. try{
  324. $html = $this->get(
  325. $proxy,
  326. $target,
  327. []
  328. );
  329. }catch(Exception $error){
  330. throw new Exception("Failed to fetch search page");
  331. }
  332. }else{
  333. //
  334. // Get authentication token
  335. //
  336. $proxy = $this->backend->get_ip();
  337. // running this will give us shit in $this->cookie
  338. // @TODO probably not needed? I get blocked anyways ffs
  339. //$this->get($proxy, "https://www.baidu.com", []);
  340. $npt_data = [
  341. "word" => $get["s"]
  342. ];
  343. // &gpc=stf%3D0%2C1752638400|stftype%3D2
  344. if(
  345. $get["older"] !== false ||
  346. $get["newer"] !== false
  347. ){
  348. if($get["older"] === false){
  349. $get["older"] = 0;
  350. }
  351. $npt_data["gpc"] = "stf={$get["older"]},{$get["newer"]}|stftype=2";
  352. }
  353. try{
  354. $html = $this->get(
  355. $proxy,
  356. "https://m.baidu.com/s",
  357. $npt_data
  358. );
  359. }catch(Exception $error){
  360. throw new Exception("Failed to fetch search page");
  361. }
  362. //$html = file_get_contents("scraper/baidu.html");
  363. }
  364. return $this->parse_search($proxy, "web", $html);
  365. }
  366. private function parse_search($proxy, $pagetype, $html){
  367. // hack, maybe not needed anymore
  368. $html = str_replace(["\n", "\r"], "", $html);
  369. $out = [
  370. "status" => "ok",
  371. "spelling" => [
  372. "type" => "no_correction",
  373. "using" => null,
  374. "correction" => null
  375. ],
  376. "npt" => null,
  377. "answer" => [],
  378. "web" => [],
  379. "image" => [],
  380. "video" => [],
  381. "news" => [],
  382. "related" => []
  383. ];
  384. $this->fuckhtml->load($html);
  385. $this->detect_ass();
  386. // get next page
  387. $npt =
  388. $this->fuckhtml
  389. ->getElementsByClassName(
  390. "new-nextpage-only",
  391. "a"
  392. );
  393. if(count($npt) === 0){
  394. $npt =
  395. $this->fuckhtml
  396. ->getElementsByClassName(
  397. "new-nextpage",
  398. "a"
  399. );
  400. }
  401. if(count($npt) !== 0){
  402. $out["npt"] =
  403. $this->backend->store(
  404. json_encode([
  405. "cookie" => $this->cookie,
  406. "target" =>
  407. $this->fuckhtml
  408. ->getTextContent(
  409. $npt[0]["attributes"]["href"]
  410. )
  411. ]),
  412. "web",
  413. $proxy
  414. );
  415. }
  416. // get "articles"
  417. $articles =
  418. $this->fuckhtml
  419. ->getElementsByTagName(
  420. "article"
  421. );
  422. foreach($articles as $article){
  423. //print_r($article);
  424. $this->fuckhtml->load($article);
  425. if(
  426. !isset($article["attributes"]["rl-link-data-ivk"]) &&
  427. !isset($article["attributes"]["data-click-info"])
  428. ){
  429. // probe for related searches
  430. $relateds =
  431. $this->fuckhtml
  432. ->getElementsByClassName(
  433. "sc-sublink",
  434. "button"
  435. );
  436. foreach($relateds as $related){
  437. $out["related"][] =
  438. $this->fuckhtml
  439. ->getTextContent(
  440. $related
  441. );
  442. }
  443. continue;
  444. }
  445. // find json object
  446. $jsons = [];
  447. if(isset($article["attributes"]["rl-link-data-ivk"])){ $jsons[] = &$article["attributes"]["rl-link-data-ivk"]; }
  448. if(isset($article["attributes"]["data-click-info"])){ $jsons[] = &$article["attributes"]["data-click-info"]; }
  449. if(isset($article["attributes"]["rl-link-data-log"])){ $jsons[] = &$article["attributes"]["rl-link-data-log"]; }
  450. // fail
  451. if(count($jsons) === 0){ continue; }
  452. $url = null;
  453. foreach($jsons as $json){
  454. $json =
  455. json_decode(
  456. $this->fuckhtml
  457. ->getTextContent(
  458. $json
  459. ),
  460. true
  461. );
  462. $url = null;
  463. if(isset($json["control"]["dataUrl"])){
  464. $url = $json["control"]["dataUrl"];
  465. break;
  466. }elseif(isset($json["control"]["default_url"])){
  467. $url = $json["control"]["default_url"];
  468. break;
  469. }elseif(isset($json["extra"])){
  470. // decode extra object
  471. $json2 = json_decode($json["extra"], true);
  472. if(isset($json2["url"])){
  473. // double chinese encryption
  474. $url = rawurldecode(rawurldecode($json2["url"]));
  475. break;
  476. }
  477. if(isset($json2["loc"])){
  478. $url = rawurldecode(rawurldecode($json2["loc"]));
  479. break;
  480. }
  481. }
  482. }
  483. // fail
  484. if($url === null){
  485. if(isset($article["attributes"]["rl-link-href"])){
  486. $url =
  487. $this->fuckhtml
  488. ->getTextContent(
  489. $article["attributes"]["rl-link-href"]
  490. );
  491. }else{
  492. // horrible fail
  493. continue;
  494. }
  495. }
  496. // get title
  497. $title =
  498. $this->fuckhtml
  499. ->getElementsByAttributeValue(
  500. "role",
  501. "title",
  502. "div"
  503. );
  504. if(count($title) === 0){
  505. $title =
  506. $this->fuckhtml
  507. ->getElementsByClassName(
  508. "cosc-title-slot",
  509. "span"
  510. );
  511. }
  512. if(count($title) === 0){
  513. $title =
  514. $this->fuckhtml
  515. ->getElementsByClassName(
  516. "cu-title",
  517. "p"
  518. );
  519. }
  520. if(count($title) !== 0){
  521. $title =
  522. $this->titledots(
  523. $this->fuckhtml
  524. ->getTextContent(
  525. $title[0]
  526. )
  527. );
  528. }else{
  529. $title = "Untitled";
  530. }
  531. // get description
  532. $description =
  533. $this->fuckhtml
  534. ->getElementsByAttributeValue(
  535. "data-module",
  536. "abstract",
  537. "div"
  538. );
  539. if(count($description) === 0){
  540. $description =
  541. $this->fuckhtml
  542. ->getElementsByAttributeValue(
  543. "role",
  544. "text",
  545. "div"
  546. );
  547. }
  548. if(count($description) === 0){
  549. $description =
  550. $this->fuckhtml
  551. ->getElementsByClassName(
  552. "cos-gutter",
  553. "div"
  554. );
  555. }
  556. if(count($description) !== 0){
  557. $description =
  558. $this->titledots(
  559. $this->fuckhtml
  560. ->getTextContent(
  561. $description[0]
  562. )
  563. );
  564. }else{
  565. $description = null;
  566. }
  567. // get date
  568. $date =
  569. $this->fuckhtml
  570. ->getElementsByClassName(
  571. "cosc-source-caption",
  572. "span"
  573. );
  574. if(count($date) !== 0){
  575. $date =
  576. $this->parse_time(
  577. $this->fuckhtml
  578. ->getTextContent(
  579. $date[0]
  580. )
  581. );
  582. }else{
  583. $date = null;
  584. }
  585. // get thumb
  586. $image =
  587. $this->fuckhtml
  588. ->getElementsByClassName(
  589. "cos-image-body",
  590. "img"
  591. );
  592. if(count($image) !== 0){
  593. $thumb = [
  594. "ratio" => "16:9",
  595. "url" =>
  596. $this->fuckhtml
  597. ->getTextContent(
  598. $image[0]["attributes"]["src"]
  599. )
  600. ];
  601. }else{
  602. $thumb = [
  603. "ratio" => null,
  604. "url" => null
  605. ];
  606. }
  607. $out["web"][] = [
  608. "title" => $title,
  609. "description" => $description,
  610. "url" => $this->redirect_add_url($proxy, $url),
  611. "date" => $date,
  612. "type" => "web",
  613. "thumb" => $thumb,
  614. "sublink" => [],
  615. "table" => []
  616. ];
  617. }
  618. // dedup related searches
  619. $out["related"] = array_values(array_unique($out["related"]));
  620. $this->resolve_urls($out);
  621. return $out;
  622. }
  623. public function image($get){
  624. // https://image.baidu.com/search/acjson?word=asmr&rn=60&pn=0&newReq=1
  625. //$json = file_get_contents("scraper/baidu_img.json");
  626. if($get["npt"]){
  627. [$params, $proxy] = $this->backend->get($get["npt"], "images");
  628. $params = json_decode($params, true);
  629. $params["pn"] = $params["pn"] + 60;
  630. }else{
  631. $proxy = $this->backend->get_ip();
  632. $params = [
  633. "word" => $get["s"],
  634. "rn" => 60, // results/page
  635. "pn" => 0, // item increment (0 * 60)
  636. "newReq" => 1 // otherwise json is fucked up
  637. ];
  638. switch($get["sort"]){
  639. case "latest": $params["latest"] = 1; break;
  640. case "hot": $params["hot"] = 1; break;
  641. }
  642. if($get["size"] != "any"){
  643. $params["z"] = $get["size"];
  644. }
  645. if($get["ratio"] != "any"){
  646. $params["imgratio"] = $get["ratio"];
  647. }
  648. if($get["format"] != "any"){
  649. $params["imgformat"] = $get["format"];
  650. }
  651. if($get["color"] != "any"){
  652. $params["ic"] = $get["color"];
  653. }
  654. switch($get["type"]){
  655. case "hd": $params["hd"] = 1; break;
  656. case "isImgSet": $params["isImgSet"] = 1; break;
  657. case "copyright": $params["copyright"] = 1; break;
  658. }
  659. }
  660. try{
  661. $json =
  662. $this->get(
  663. $proxy,
  664. "https://image.baidu.com/search/acjson",
  665. $params,
  666. "https://image.baidu.com/search/index?tn=baiduimage&word=" . urlencode($get["s"])
  667. );
  668. }catch(Exception $error){
  669. throw new Exception("Failed to fetch JSON");
  670. }
  671. $json = json_decode($json, true);
  672. if($json === null){
  673. // detect captcha first
  674. $this->fuckhtml->load($json);
  675. $this->detect_ass();
  676. // fallback to json decode error
  677. throw new Exception("Failed to decode JSON");
  678. }
  679. if(
  680. isset($json["message"]) &&
  681. $json["message"] != "success"
  682. ){
  683. throw new Exception("Baidu returned an error: {$json["message"]}");
  684. }
  685. if(!isset($json["data"]["images"])){
  686. throw new Exception("Baidu did not return an image object");
  687. }
  688. $out = [
  689. "status" => "ok",
  690. "npt" => null,
  691. "image" => []
  692. ];
  693. foreach($json["data"]["images"] as $image){
  694. parse_str(parse_url($image["thumburl"], PHP_URL_QUERY), $thumb_size);
  695. $out["image"][] = [
  696. "title" =>
  697. $this->fuckhtml
  698. ->getTextContent(
  699. $image["titleShow"]
  700. ),
  701. "source" => [
  702. [
  703. "url" => $image["objurl"],
  704. "width" => (int)$image["width"],
  705. "height" => (int)$image["height"]
  706. ],
  707. [ // thumbnail
  708. "url" => $image["thumburl"],
  709. "width" => (int)$thumb_size["w"],
  710. "height" => (int)$thumb_size["h"]
  711. ]
  712. ],
  713. "url" => $image["fromUrl"]
  714. ];
  715. }
  716. //
  717. // Detect if there's a next page
  718. //
  719. if((int)$json["data"]["totalNum"] >= $params["pn"] + 60){
  720. $out["npt"] =
  721. $this->backend->store(
  722. json_encode($params),
  723. "images",
  724. $proxy
  725. );
  726. }
  727. return $out;
  728. }
  729. public function video($get){
  730. // https://www.baidu.com/sf/vsearch?pd=video&tn=vsearch&wd=jak%2Band%2Bdaxter&async=1&pn=0
  731. // increase &pn +20 for pagination
  732. //$html = file_get_contents("scraper/baidu_vid.html");
  733. if($get["npt"]){
  734. [$params, $proxy] = $this->backend->get($get["npt"], "videos");
  735. $params = json_decode($params, true);
  736. $params["pn"] = $params["pn"] + 10;
  737. }else{
  738. $proxy = $this->backend->get_ip();
  739. $params = [
  740. "pd" => "video",
  741. "tn" => "vsearch",
  742. "wd" => $get["s"],
  743. "async" => 1,
  744. "pn" => 0
  745. ];
  746. }
  747. try{
  748. $html =
  749. $this->get(
  750. $proxy,
  751. "https://www.baidu.com/sf/vsearch",
  752. $params
  753. );
  754. }catch(Exception $error){
  755. throw new Exception("Failed to get search page");
  756. }
  757. $html =
  758. str_replace(
  759. ["\r", "\n"],
  760. "",
  761. $html
  762. );
  763. $out = [
  764. "status" => "ok",
  765. "npt" => null,
  766. "video" => [],
  767. "author" => [],
  768. "livestream" => [],
  769. "playlist" => [],
  770. "reel" => []
  771. ];
  772. $html = explode("<script>", $html);
  773. foreach($html as $result){
  774. $result = trim($result);
  775. $this->fuckhtml->load($result);
  776. // get URL
  777. preg_match(
  778. '/<!-- *([^ ]*) *-->/',
  779. $result,
  780. $matches
  781. );
  782. if(!isset($matches[1])){
  783. // no link, give up
  784. continue;
  785. }
  786. $link = $matches[1];
  787. // get title
  788. $title =
  789. $this->fuckhtml
  790. ->getElementsByClassName(
  791. "video-title",
  792. "a"
  793. );
  794. if(count($title) === 0){
  795. // should not happen
  796. continue;
  797. }
  798. $title =
  799. $this->fuckhtml
  800. ->getTextContent(
  801. $title[0]
  802. );
  803. // get thumbnail
  804. $img =
  805. $this->fuckhtml
  806. ->getElementsByClassName(
  807. "border-radius",
  808. "img"
  809. );
  810. if(count($img) !== 0){
  811. $thumb = [
  812. "url" =>
  813. $this->unfuckthumb(
  814. $this->fuckhtml
  815. ->getTextContent(
  816. $img[0]["attributes"]["src"]
  817. )
  818. ),
  819. "ratio" => "16:9"
  820. ];
  821. }else{
  822. $thumb = [
  823. "url" => null,
  824. "ratio" => null
  825. ];
  826. }
  827. $span =
  828. $this->fuckhtml
  829. ->getElementsByTagName(
  830. "span"
  831. );
  832. // get duration
  833. $duration =
  834. $this->fuckhtml
  835. ->getElementsByClassName(
  836. "video_play_timer",
  837. $span
  838. );
  839. if(count($duration) !== 0){
  840. $duration =
  841. $this->hms2int(
  842. $this->fuckhtml
  843. ->getTextContent(
  844. $duration[0]
  845. )
  846. );
  847. }else{
  848. $duration = null;
  849. }
  850. // get author
  851. // 来源:哔哩哔哩
  852. $author =
  853. $this->fuckhtml
  854. ->getElementsByClassName(
  855. "wetSource",
  856. $span
  857. );
  858. if(count($author) !== 0){
  859. $author =
  860. explode(
  861. ":",
  862. $this->fuckhtml
  863. ->getTextContent(
  864. $author[0]
  865. ),
  866. 2
  867. )[1];
  868. }else{
  869. $author = null;
  870. }
  871. // get date posted
  872. //发布时间:2024-05-06
  873. // AND get description
  874. // 简介:Our first look
  875. $infospans =
  876. array_merge(
  877. $this->fuckhtml
  878. ->getElementsByClassName(
  879. "c-font-normal",
  880. $span
  881. ),
  882. $this->fuckhtml
  883. ->getElementsByClassName(
  884. "c-font-normal",
  885. "div"
  886. )
  887. );
  888. $date = null;
  889. $description = null;
  890. foreach($infospans as $infospan){
  891. $infospan =
  892. explode(
  893. ":",
  894. $this->fuckhtml
  895. ->getTextContent(
  896. $infospan
  897. ),
  898. 2
  899. );
  900. if(count($infospan) !== 2){
  901. // should not happen
  902. continue;
  903. }
  904. $infospan[1] =
  905. $this->fuckhtml
  906. ->getTextContent(
  907. $infospan[1]
  908. );
  909. switch($infospan[0]){
  910. case "发布时间": // date posted
  911. $date = $this->parse_time($infospan[1]);
  912. break;
  913. case "简介": // description
  914. $description = $infospan[1];
  915. break;
  916. }
  917. }
  918. $out["video"][] = [
  919. "title" => $this->titledots($title),
  920. "description" => $this->titledots($description),
  921. "author" => [
  922. "name" => $author,
  923. "url" => null,
  924. "avatar" => null
  925. ],
  926. "date" => $date,
  927. "duration" => $duration,
  928. "views" => null,
  929. "thumb" => $thumb,
  930. "url" => $link
  931. ];
  932. }
  933. if(count($out["video"]) === 10){
  934. // assume there's another page after this
  935. $out["npt"] =
  936. $this->backend->store(
  937. json_encode($params),
  938. "videos",
  939. $proxy
  940. );
  941. }
  942. return $out;
  943. }
  944. /*
  945. public function news($get){
  946. //$proxy = $this->backend->get_ip();
  947. //$html = file_get_contents("scraper/baidu.html");
  948. //$npt_data = [];
  949. if($get["npt"]){
  950. [$json, $proxy] = $this->backend->get($get["npt"], "news");
  951. $json = json_decode($json, true);
  952. $this->cookie = $json["cookie"];
  953. $npt_data = $json["req"];
  954. $npt_data["pn"] = $npt_data["pn"] + 20;
  955. try{
  956. $html = $this->get(
  957. $proxy,
  958. "https://www.baidu.com/s",
  959. $npt_data
  960. );
  961. }catch(Exception $error){
  962. throw new Exception("Failed to fetch search page");
  963. }
  964. }else{
  965. //
  966. // Get authentication token
  967. //
  968. $proxy = $this->backend->get_ip();
  969. $npt_data = [
  970. "wd" => $get["s"],
  971. "rn" => 20,
  972. "tn" => "news"
  973. ];
  974. // @TODO add filters
  975. try{
  976. $html = $this->get(
  977. $proxy,
  978. "https://www.baidu.com/s",
  979. $npt_data
  980. );
  981. }catch(Exception $error){
  982. throw new Exception("Failed to fetch search page");
  983. }
  984. $npt_data["pn"] = 0;
  985. }
  986. $data = $this->parse_search($proxy, "news", $npt_data, $html);
  987. $out = [
  988. "status" => "ok",
  989. "npt" => $data["npt"],
  990. "news" => []
  991. ];
  992. foreach($data["web"] as $article){
  993. $out["news"][] = [
  994. "title" => $article["title"],
  995. "author" => null,
  996. "description" => $article["description"],
  997. "date" => $article["date"],
  998. "thumb" => [
  999. "url" => $article["thumb"]["url"],
  1000. "ratio" => $article["thumb"]["url"] !== null ? "16:9" : null,
  1001. ],
  1002. "url" => $article["url"]
  1003. ];
  1004. }
  1005. return $out;
  1006. }*/
  1007. private function unfuckthumb($url){
  1008. // probe for proxy URL
  1009. $parsed_url = parse_url($url);
  1010. if(
  1011. preg_match(
  1012. '/^https?:\/\/gimg(?:[0-9]+)?\.baidu\.com/',
  1013. $url
  1014. )
  1015. ){
  1016. $parts = explode("src=", $url);
  1017. if(count($parts) !== 2){
  1018. // shits fucked
  1019. return $url;
  1020. }
  1021. return urldecode(explode("&", $parts[1])[0]);
  1022. }
  1023. $q = explode("&", $url, 2);
  1024. if(count($q) !== 2){
  1025. // shits fucked, again
  1026. return $url;
  1027. }
  1028. // baidu devs are fucking retarded and dont follow spec:
  1029. // &fmt=auto?s=BB32F3A050471AEC72886934030090C4&sec=1753203600&t=0fb2194775d3bd3d1bb114b818479e0a
  1030. parse_str(str_replace("?", "&", $q[1]), $query);
  1031. if(isset($query["size"])){ unset($query["size"]); }
  1032. if(isset($query["q"])){ $query["q"] = "90"; }
  1033. $query = http_build_query($query);
  1034. return
  1035. str_replace(
  1036. $q[1],
  1037. $query,
  1038. $url
  1039. );
  1040. }
  1041. private function titledots($title){
  1042. return trim($title, " .\t\n\r\0\x0B…");
  1043. }
  1044. private function hms2int($time){
  1045. $parts = explode(":", $time, 3);
  1046. $time = 0;
  1047. if(count($parts) === 3){
  1048. // hours
  1049. $time = $time + ((int)$parts[0] * 3600);
  1050. array_shift($parts);
  1051. }
  1052. if(count($parts) === 2){
  1053. // minutes
  1054. $time = $time + ((int)$parts[0] * 60);
  1055. array_shift($parts);
  1056. }
  1057. // seconds
  1058. $time = $time + (int)$parts[0];
  1059. return $time;
  1060. }
  1061. private function parse_viewcount($views){
  1062. if(
  1063. // 10k (wtf lol)
  1064. preg_match(
  1065. '/([0-9]+)万次/',
  1066. $views,
  1067. $matches
  1068. )
  1069. ){
  1070. return (int)$matches[1] * 10000;
  1071. }
  1072. if(
  1073. // units
  1074. preg_match(
  1075. '/([0-9]+)次/',
  1076. $views,
  1077. $matches
  1078. )
  1079. ){
  1080. return (int)$matches[1];
  1081. }
  1082. return null;
  1083. }
  1084. private function parse_time($time){
  1085. // 2023年8月7日 => yyyy/m/d
  1086. if(
  1087. preg_match(
  1088. '/([0-9]{4})年([0-9]{1,2})月([0-9]{1,2})日/',
  1089. $time,
  1090. $matches
  1091. )
  1092. ){
  1093. return strtotime("{$matches[1]}/{$matches[2]}/{$matches[3]}");
  1094. }
  1095. // 昨天11:45 => yesterday at 11:45
  1096. // 昨天 => yesterday
  1097. if(
  1098. preg_match(
  1099. '/昨天(.*)/',
  1100. $time,
  1101. $matches
  1102. )
  1103. ){
  1104. return strtotime("Yesterday {$matches[1]}");
  1105. }
  1106. // 3天前 => 3 days ago
  1107. if(
  1108. preg_match(
  1109. '/([0-9]{1,4})天前/',
  1110. $time,
  1111. $matches
  1112. )
  1113. ){
  1114. return strtotime("{$matches[1]} days ago");
  1115. }
  1116. // 1个月前 => 1 month ago
  1117. if(
  1118. preg_match(
  1119. '/([0-9]{1,4})个月前/',
  1120. $time,
  1121. $matches
  1122. )
  1123. ){
  1124. return strtotime("{$matches[1]} months ago");
  1125. }
  1126. // attempt to parse as-is
  1127. $time = strtotime($time);
  1128. if($time !== false){
  1129. return $time;
  1130. }
  1131. return null;
  1132. }
  1133. private function detect_ass(){
  1134. $as =
  1135. $this->fuckhtml
  1136. ->getElementsByTagName(
  1137. "a"
  1138. );
  1139. if(
  1140. count($as) === 0 ||
  1141. preg_match(
  1142. '/^https?:\/\/wappass\.baidu\.com\/static\/captcha/',
  1143. $this->fuckhtml
  1144. ->getTextContent(
  1145. $as[0]["attributes"]["href"]
  1146. )
  1147. )
  1148. ){
  1149. throw new Exception("Baidu returned a Captcha");
  1150. }
  1151. }
  1152. }