baidu.php 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243
  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->handles = [];
  9. $this->proc = null;
  10. $this->handle_category = null;
  11. $this->handle_increment = 0;
  12. $this->sublink_increment = 0;
  13. $this->cookie = null;
  14. }
  15. public function getfilters($page){
  16. switch($page){
  17. case "web":
  18. return
  19. [
  20. "newer" => [
  21. "display" => "Newer than",
  22. "option" => "_DATE"
  23. ],
  24. "older" => [
  25. "display" => "Older than",
  26. "option" => "_DATE"
  27. ]
  28. ];
  29. break;
  30. case "images":
  31. return
  32. [
  33. "sort" => [
  34. "display" => "Sort",
  35. "option" => [
  36. "relevance" => "Relevance", // no param
  37. "latest" => "Latest", // &latest=1
  38. "hot" => "Hot" // &hot=1
  39. ]
  40. ],
  41. "size" => [
  42. "display" => "Size",
  43. "option" => [
  44. "any" => "Any size",
  45. "7" => "Extra large (1080px+)", // &z=7
  46. "6" => "Large (600px~1080px)", // &z=6
  47. "5" => "Medium (300px~600px)", // &z=5
  48. "4" => "Small (1px~300px)" // &z=4
  49. ]
  50. ],
  51. "ratio" => [
  52. "display" => "Ratio",
  53. "option" => [
  54. "any" => "Any ratio",
  55. "1" => "Tall vertical", // &imgratio=1
  56. "2" => "Vertical", // &imgratio=2
  57. "3" => "Square", // &imgratio=3
  58. "4" => "Horizontal", // &imgratio=4
  59. "5" => "Wide horizontal" // &imgratio=5
  60. ]
  61. ],
  62. "format" => [
  63. "display" => "Format",
  64. "option" => [
  65. "any" => "Any format",
  66. "3" => "JPG", // &imgformat=3
  67. "5" => "JPEG", // &imgformat=5
  68. "4" => "PNG", // &imgformat=4
  69. "2" => "BMP", // &imgformat=2
  70. "6" => "GIF (Animated)" // &imgformat=6
  71. ]
  72. ],
  73. "color" => [
  74. "display" => "Color",
  75. "option" => [
  76. "any" => "Any color",
  77. "1024" => "White", // &ic=1024
  78. "2048" => "Black & White",
  79. "512" => "Black",
  80. "64" => "Magenta",
  81. "16" => "Blue",
  82. "1" => "Red",
  83. "2" => "Yellow",
  84. "32" => "Purple",
  85. "4" => "Green",
  86. "8" => "Teal",
  87. "256" => "Orange",
  88. "128" => "Brown"
  89. ]
  90. ],
  91. "type" => [
  92. "display" => "Type",
  93. "option" => [
  94. "any" => "Any type",
  95. "hd" => "HD", // &hd=1
  96. "isImgSet" => "Photo album", // &isImgSet=1
  97. "copyright" => "Copyright" // &copyright=1
  98. ]
  99. ]
  100. ];
  101. break;
  102. case "videos":
  103. return [];
  104. break;
  105. case "news":
  106. return [
  107. "category" => [
  108. "display" => "Category",
  109. "option" => [
  110. "any" => "All news",
  111. "media" => "Media websites", // &medium=1
  112. "baijiahao" => "Baidu Baijiahao" // &medium=2
  113. ]
  114. ]
  115. ];
  116. break;
  117. }
  118. }
  119. private function get($proxy, $url, $get = [], $referer = false){
  120. $curlproc = curl_init();
  121. if($get !== []){
  122. $get = http_build_query($get);
  123. $url .= "?" . $get;
  124. }
  125. $cookies_tmp = [];
  126. curl_setopt($curlproc, CURLOPT_HEADERFUNCTION, function($curlproc, $header) use (&$cookies_tmp){
  127. $length = strlen($header);
  128. $header = explode(":", $header, 2);
  129. if(trim(strtolower($header[0])) == "set-cookie"){
  130. $cookie_tmp = explode("=", trim($header[1]), 2);
  131. $cookies_tmp[trim($cookie_tmp[0])] =
  132. explode(";", $cookie_tmp[1], 2)[0];
  133. }
  134. return $length;
  135. });
  136. curl_setopt($curlproc, CURLOPT_URL, $url);
  137. curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
  138. if($referer === false){
  139. if($this->cookie === null){
  140. curl_setopt($curlproc, CURLOPT_HTTPHEADER,
  141. ["User-Agent: " . config::USER_AGENT,
  142. "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
  143. "Accept-Language: en-US,en;q=0.5",
  144. "Accept-Encoding: gzip, deflate, br, zstd",
  145. "DNT: 1",
  146. "Sec-GPC: 1",
  147. "Connection: keep-alive",
  148. "Upgrade-Insecure-Requests: 1",
  149. "Sec-Fetch-Dest: document",
  150. "Sec-Fetch-Mode: navigate",
  151. "Sec-Fetch-Site: cross-site",
  152. "Priority: u=0, i"]
  153. );
  154. }else{
  155. curl_setopt($curlproc, CURLOPT_HTTPHEADER,
  156. ["User-Agent: " . config::USER_AGENT,
  157. "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
  158. "Accept-Language: en-US,en;q=0.5",
  159. "Accept-Encoding: gzip, deflate, br, zstd",
  160. "DNT: 1",
  161. "Sec-GPC: 1",
  162. "Connection: keep-alive",
  163. "Cookie: {$this->cookie}",
  164. "Upgrade-Insecure-Requests: 1",
  165. "Sec-Fetch-Dest: document",
  166. "Sec-Fetch-Mode: navigate",
  167. "Sec-Fetch-Site: cross-site",
  168. "Priority: u=0, i"]
  169. );
  170. }
  171. }else{
  172. if($this->cookie === null){
  173. curl_setopt($curlproc, CURLOPT_HTTPHEADER,
  174. ["User-Agent: " . config::USER_AGENT,
  175. "Accept: application/json, text/plain, */*",
  176. "Accept-Language: en-US,en;q=0.5",
  177. "Accept-Encoding: gzip, deflate, br, zstd",
  178. "Referer: {$referer}",
  179. "DNT: 1",
  180. "Sec-GPC: 1",
  181. "Connection: keep-alive",
  182. "Upgrade-Insecure-Requests: 1",
  183. "Sec-Fetch-Dest: empty",
  184. "Sec-Fetch-Mode: cors",
  185. "Sec-Fetch-Site: same-origin"]
  186. );
  187. }else{
  188. curl_setopt($curlproc, CURLOPT_HTTPHEADER,
  189. ["User-Agent: " . config::USER_AGENT,
  190. "Accept: application/json, text/plain, */*",
  191. "Accept-Language: en-US,en;q=0.5",
  192. "Accept-Encoding: gzip, deflate, br, zstd",
  193. "Referer: {$referer}",
  194. "DNT: 1",
  195. "Sec-GPC: 1",
  196. "Connection: keep-alive",
  197. "Cookie: {$this->cookie}",
  198. "Upgrade-Insecure-Requests: 1",
  199. "Sec-Fetch-Dest: empty",
  200. "Sec-Fetch-Mode: cors",
  201. "Sec-Fetch-Site: same-origin"]
  202. );
  203. }
  204. }
  205. curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
  206. curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
  207. curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
  208. curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
  209. curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
  210. $this->backend->assign_proxy($curlproc, $proxy);
  211. $data = curl_exec($curlproc);
  212. if(curl_errno($curlproc)){
  213. throw new Exception(curl_error($curlproc));
  214. }
  215. // store cookie
  216. if(strlen($this->cookie) !== 0){
  217. $this->cookie .= "; ";
  218. }
  219. foreach($cookies_tmp as $cookie_name => $cookie_value){
  220. $this->cookie .= $cookie_name . "=" . $cookie_value . "; ";
  221. }
  222. $this->cookie = rtrim($this->cookie, " ;");
  223. curl_close($curlproc);
  224. return $data;
  225. }
  226. private function redirect_add_url($proxy, $url){
  227. if(
  228. preg_match(
  229. '/^https?:\/\/(?:www\.)?baidu\.com\/link\?/',
  230. $url
  231. ) === 0
  232. ){
  233. // not a baidu redirect
  234. return;
  235. }
  236. $curlproc = curl_init();
  237. curl_setopt($curlproc, CURLOPT_URL, $url);
  238. curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
  239. curl_setopt($curlproc, CURLOPT_HTTPHEADER,
  240. ["User-Agent: " . config::USER_AGENT,
  241. "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
  242. "Accept-Language: en-US,en;q=0.5",
  243. "Accept-Encoding: gzip, deflate, br, zstd",
  244. "DNT: 1",
  245. "Sec-GPC: 1",
  246. "Connection: keep-alive",
  247. "Upgrade-Insecure-Requests: 1",
  248. "Sec-Fetch-Dest: document",
  249. "Sec-Fetch-Mode: navigate",
  250. "Sec-Fetch-Site: none",
  251. "Sec-Fetch-User: ?1",
  252. "Priority: u=0, i"]
  253. );
  254. curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
  255. curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
  256. curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
  257. curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
  258. curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
  259. curl_setopt($curlproc, CURLOPT_HEADER, true);
  260. curl_setopt($curlproc, CURLOPT_NOBODY, true);
  261. $this->backend->assign_proxy($curlproc, $proxy);
  262. curl_multi_add_handle($this->proc, $curlproc);
  263. $this->handles[$this->handle_category][$this->handle_increment][$this->sublink_increment] = $curlproc;
  264. }
  265. private function resolve_urls($proxy, &$collection, $categories){
  266. $this->proc = curl_multi_init();
  267. curl_multi_select($this->proc);
  268. foreach($categories as $category){
  269. $this->sublink_increment = 0;
  270. $this->handle_increment = 0;
  271. $this->handle_category = $category;
  272. foreach($collection[$category] as $item){
  273. $this->sublink_increment = 0;
  274. $this->redirect_add_url($proxy, $item["url"]);
  275. if(isset($item["sublink"])){
  276. foreach($item["sublink"] as $sublink){
  277. $this->sublink_increment++;
  278. $this->redirect_add_url($proxy, $sublink["url"]);
  279. }
  280. }
  281. $this->handle_increment++;
  282. }
  283. }
  284. do{
  285. $status = curl_multi_exec($this->proc, $active);
  286. }while($active && $status == CURLM_OK);
  287. //
  288. // if we reach this, we're done downloading garbage
  289. //
  290. foreach($this->handles as $category => $v){
  291. foreach($v as $index => $data){
  292. foreach($this->handles[$category][$index] as $sublinkindex => $handle){
  293. preg_match(
  294. '/location: ?(.*)$/im',
  295. curl_multi_getcontent($handle),
  296. $location
  297. );
  298. if(isset($location[1])){
  299. if($sublinkindex === 0){
  300. $collection[$category][$index]["url"] = trim($location[1]);
  301. }else{
  302. $collection[$category][$index]["sublink"][$sublinkindex - 1]["url"] = trim($location[1]);
  303. }
  304. }
  305. curl_multi_remove_handle($this->proc, $handle);
  306. curl_close($handle);
  307. }
  308. }
  309. }
  310. curl_multi_close($this->proc);
  311. }
  312. private function resolve_images($proxy, &$data){
  313. // get the image viewer that contains all of the images direct URLs
  314. // for some reason, getting the second image's url in the set
  315. // doesnt trigger the captcha
  316. if(
  317. !isset($data["image"][1]["url"]) ||
  318. preg_match(
  319. '/^https:\/\/image\.baidu\.com\/search\/detail/',
  320. $data["image"][1]["url"]
  321. ) === 0
  322. ){
  323. // we have an already resolved image link, do nothing
  324. return;
  325. }
  326. try{
  327. $html =
  328. $this->get(
  329. $proxy,
  330. $data["image"][1]["url"],
  331. []
  332. );
  333. }catch(Exception $error){
  334. // fallback to the limited dataset we have
  335. return;
  336. }
  337. $this->fuckhtml->load($html);
  338. $script =
  339. $this->fuckhtml
  340. ->getElementById(
  341. "image-detail-data",
  342. "script"
  343. );
  344. if($script){
  345. $json =
  346. json_decode(
  347. $script["innerHTML"],
  348. true
  349. );
  350. if(
  351. !isset($json["data"]["images"]) ||
  352. count($json["data"]["images"]) === 0
  353. ){
  354. // do nothing
  355. return;
  356. }
  357. //
  358. // Discard all previously scraped images and use data
  359. // from the newly downloaded image carousel
  360. // the imageset !!should!! be the same
  361. //
  362. $data["image"] = [];
  363. foreach($json["data"]["images"] as $image){
  364. parse_str(parse_url($image["thumburl"], PHP_URL_QUERY), $thumb_size);
  365. $data["image"][] = [
  366. "title" =>
  367. $this->fuckhtml
  368. ->getTextContent(
  369. $image["titleShow"]
  370. ),
  371. "source" => [
  372. [
  373. "url" => $image["objurl"],
  374. "width" => (int)$image["width"],
  375. "height" => (int)$image["height"]
  376. ],
  377. [ // thumbnail
  378. "url" => $image["thumburl"],
  379. "width" => (int)$thumb_size["w"],
  380. "height" => (int)$thumb_size["h"]
  381. ]
  382. ],
  383. "url" => $image["fromUrl"]
  384. ];
  385. }
  386. }
  387. }
  388. public function web($get){
  389. if($get["npt"]){
  390. [$json, $proxy] = $this->backend->get($get["npt"], "web");
  391. $json = json_decode($json, true);
  392. $this->cookie = $json["cookie"];
  393. $npt_data = $json["req"];
  394. $npt_data["pn"] = $npt_data["pn"] + 20;
  395. try{
  396. $html = $this->get(
  397. $proxy,
  398. "https://www.baidu.com/s",
  399. $npt_data
  400. );
  401. }catch(Exception $error){
  402. throw new Exception("Failed to fetch search page");
  403. }
  404. }else{
  405. //
  406. // Get authentication token
  407. //
  408. $proxy = $this->backend->get_ip();
  409. // running this will give us shit in $this->cookie
  410. // @TODO probably not needed? I get blocked anyways ffs
  411. //$this->get($proxy, "https://www.baidu.com", []);
  412. $npt_data = [
  413. "wd" => $get["s"],
  414. "rn" => 20
  415. ];
  416. // &gpc=stf%3D0%2C1752638400|stftype%3D2
  417. if(
  418. $get["older"] !== false ||
  419. $get["newer"] !== false
  420. ){
  421. if($get["older"] === false){
  422. $get["older"] = 0;
  423. }
  424. $npt_data["gpc"] = "stf={$get["older"]},{$get["newer"]}|stftype=2";
  425. }
  426. try{
  427. $html = $this->get(
  428. $proxy,
  429. "https://www.baidu.com/s",
  430. $npt_data
  431. );
  432. }catch(Exception $error){
  433. throw new Exception("Failed to fetch search page");
  434. }
  435. $npt_data["pn"] = 0;
  436. }
  437. return $this->parse_search($proxy, "web", $npt_data, $html);
  438. }
  439. private function parse_search($proxy, $pagetype, $npt_data, $html){
  440. // @HACK
  441. // remove newlines from the html, cause it fucks with fuckhtml
  442. $html = str_replace(["\n", "\r"], "", $html);
  443. $out = [
  444. "status" => "ok",
  445. "spelling" => [
  446. "type" => "no_correction",
  447. "using" => null,
  448. "correction" => null
  449. ],
  450. "npt" => null,
  451. "answer" => [],
  452. "web" => [],
  453. "image" => [],
  454. "video" => [],
  455. "news" => [],
  456. "related" => []
  457. ];
  458. $this->fuckhtml->load($html);
  459. $this->detect_ass();
  460. $datafields =
  461. $this->fuckhtml
  462. ->getElementsByAttributeName(
  463. "id",
  464. "div"
  465. );
  466. //
  467. // Get next page
  468. //
  469. $npt =
  470. $this->fuckhtml
  471. ->getElementsByClassName(
  472. "n",
  473. "a"
  474. );
  475. if(count($npt) !== 0){
  476. $out["npt"] =
  477. $this->backend->store(
  478. json_encode([
  479. "req" => $npt_data,
  480. "cookie" => $this->cookie
  481. ]),
  482. $pagetype,
  483. $proxy
  484. );
  485. }
  486. //
  487. // Get related searches
  488. //
  489. $related_container =
  490. $this->fuckhtml
  491. ->getElementById(
  492. "rs_new",
  493. $datafields
  494. );
  495. if($related_container){
  496. $this->fuckhtml->load($related_container);
  497. $as =
  498. $this->fuckhtml
  499. ->getElementsByClassName(
  500. "c-color-link",
  501. "a"
  502. );
  503. foreach($as as $a){
  504. $text =
  505. explode(
  506. ">",
  507. $this->fuckhtml
  508. ->getTextContent(
  509. $a
  510. ),
  511. 2
  512. );
  513. $out["related"][] = $text[count($text) - 1];
  514. }
  515. }
  516. foreach($datafields as $datafield){
  517. if(
  518. !isset($datafield["attributes"]["id"]) ||
  519. preg_match(
  520. '/^[0-9]+$/',
  521. $datafield["attributes"]["id"]
  522. ) === 0
  523. ){
  524. // not a search result
  525. continue;
  526. }
  527. $this->fuckhtml->load($datafield);
  528. $div =
  529. $this->fuckhtml
  530. ->getElementsByTagName(
  531. "div"
  532. );
  533. //
  534. // Don't parse as a search result if it's a card
  535. //
  536. $tpl_probe =
  537. $this->fuckhtml
  538. ->getElementsByAttributeValue(
  539. "tpl",
  540. "www_index",
  541. [$datafield]
  542. );
  543. if(
  544. count($tpl_probe) === 0 &&
  545. count(
  546. $this->fuckhtml
  547. ->getElementsByClassName(
  548. "cosc-card",
  549. $div
  550. )
  551. ) !== 0
  552. ){
  553. //
  554. // Parse chinese youtube shorts
  555. //
  556. $ytshorts_probe =
  557. $this->fuckhtml
  558. ->getElementsByClassName(
  559. "tts-b-item",
  560. $div
  561. );
  562. if(count($ytshorts_probe) !== 0){
  563. $videos =
  564. $this->fuckhtml
  565. ->getElementsByAttributeValue(
  566. "data-show",
  567. "list",
  568. $div
  569. );
  570. foreach($videos as $video){
  571. $this->fuckhtml->load($video);
  572. $title =
  573. $this->fuckhtml
  574. ->getElementsByClassName(
  575. "cosc-title-slot",
  576. "span"
  577. );
  578. if(count($title) === 0){
  579. continue;
  580. }
  581. $url =
  582. $this->fuckhtml
  583. ->getElementsByTagName(
  584. "a"
  585. );
  586. if(count($url) === 0){
  587. continue;
  588. }
  589. $image =
  590. $this->fuckhtml
  591. ->getElementsByClassName(
  592. "cos-image-body",
  593. "img"
  594. );
  595. if(count($image) === 0){
  596. $image = [
  597. "ratio" => null,
  598. "url" => null
  599. ];
  600. }else{
  601. $image = [
  602. "ratio" => "1:1",
  603. "url" =>
  604. $this->fuckhtml
  605. ->getTextContent(
  606. $image[0]["attributes"]["src"]
  607. )
  608. ];
  609. }
  610. // get duration
  611. $divs =
  612. $this->fuckhtml
  613. ->getElementsByAttributeName(
  614. "class",
  615. "div"
  616. );
  617. $duration = null;
  618. foreach($divs as $probe){
  619. if(strpos($probe["attributes"]["class"], "tag-bottom-right") !== false){
  620. $duration =
  621. $this->hms2int(
  622. $this->fuckhtml
  623. ->getTextContent(
  624. $probe
  625. )
  626. );
  627. break;
  628. }
  629. }
  630. $out["video"][] = [
  631. "title" =>
  632. $this->fuckhtml
  633. ->getTextContent(
  634. $title[0]
  635. ),
  636. "description" => null,
  637. "date" => null,
  638. "duration" => $duration,
  639. "views" => null,
  640. "thumb" => $image,
  641. "url" =>
  642. $this->fuckhtml
  643. ->getTextContent(
  644. $url[0]["attributes"]["href"]
  645. )
  646. ];
  647. }
  648. }
  649. //
  650. // Parse image carousel
  651. //
  652. $is_image_carousel = false;
  653. foreach($div as $d){
  654. if(
  655. isset($d["attributes"]["class"]) &&
  656. strpos($d["attributes"]["class"], "image-container") !== false
  657. ){
  658. $is_image_carousel = true;
  659. break;
  660. }
  661. }
  662. if($is_image_carousel){
  663. preg_match(
  664. '/<!--s-data:([\S\s]*)-->/U',
  665. $datafield["innerHTML"],
  666. $matches
  667. );
  668. if(isset($matches[1])){
  669. // weird behavior with the smaller image carousel where --cos* CSS variables are escaped wrong
  670. $json =
  671. $this->fuckhtml
  672. ->parseJsObject(
  673. str_replace(
  674. "-\-",
  675. "--",
  676. $matches[1]
  677. )
  678. );
  679. if(
  680. $json !== null &&
  681. isset($json["imageList"][0]["images"])
  682. ){
  683. // parse image carousel
  684. foreach($json["imageList"][0]["images"] as $image){
  685. parse_str(parse_url($image["thumburl"], PHP_URL_QUERY), $thumb_size);
  686. $out["image"][] = [
  687. "title" => "image",
  688. "source" => [
  689. [
  690. "url" => $image["objurl"],
  691. "width" => (int)$image["width"],
  692. "height" => (int)$image["height"]
  693. ],
  694. [ // thumbnail
  695. "url" => $image["thumburl"],
  696. "width" => (int)$thumb_size["w"],
  697. "height" => (int)$thumb_size["h"]
  698. ]
  699. ],
  700. "url" => $image["jumpUrl"]
  701. ];
  702. }
  703. }
  704. }
  705. }
  706. continue;
  707. }
  708. if(!isset($datafield["attributes"]["mu"])){
  709. // dont scrape if we dont have the direct link
  710. continue;
  711. }
  712. // class:FYB_RD -> News garbage, IGNORE
  713. $result =
  714. array_merge(
  715. $tpl_probe,
  716. $this->fuckhtml
  717. ->getElementsByClassName(
  718. "result",
  719. [$datafield]
  720. )
  721. );
  722. if(count($result) !== 0){
  723. //
  724. // Parse normal search result
  725. //
  726. $title =
  727. $this->fuckhtml
  728. ->getElementsByClassName(
  729. "cosc-title-a",
  730. "a"
  731. );
  732. if(count($title) === 0){
  733. // should not happen
  734. continue;
  735. }
  736. $title =
  737. $this->titledots(
  738. $this->fuckhtml
  739. ->getTextContent(
  740. $title[0]
  741. )
  742. );
  743. $description =
  744. $this->fuckhtml
  745. ->getElementsByAttributeValue(
  746. "data-sanssr-cmpt",
  747. "card/www-summary-1",
  748. $div
  749. );
  750. if(count($description) !== 0){
  751. $this->fuckhtml->load($description[0]);
  752. $description =
  753. $this->fuckhtml
  754. ->getElementsByAttributeName(
  755. "class",
  756. "span"
  757. );
  758. $found_desc = false;
  759. foreach($description as $desc){
  760. if(stripos($desc["attributes"]["class"], "summary-text") !== false){
  761. $found_desc = true;
  762. $description =
  763. $this->titledots(
  764. $this->fuckhtml
  765. ->getTextContent(
  766. $desc
  767. )
  768. );
  769. break;
  770. }
  771. }
  772. if($found_desc === false){
  773. $description = null;
  774. }
  775. $this->fuckhtml->load($datafield);
  776. }else{
  777. $description = null;
  778. }
  779. // parse date
  780. $date_probe =
  781. $this->fuckhtml
  782. ->getElementsByClassName(
  783. "cos-color-text-minor",
  784. "span"
  785. );
  786. if(count($date_probe) !== 0){
  787. $date =
  788. $this->parse_time(
  789. $this->fuckhtml
  790. ->getTextContent(
  791. $date_probe[0]
  792. )
  793. );
  794. }else{
  795. $date = null;
  796. }
  797. // parse image
  798. $img =
  799. $this->fuckhtml
  800. ->getElementsByTagName(
  801. "img"
  802. );
  803. if(count($img) !== 0){
  804. $image = [
  805. "ratio" => "16:9",
  806. "url" =>
  807. $this->unfuckthumb(
  808. $this->fuckhtml
  809. ->getTextContent(
  810. $img[0]["attributes"]["src"]
  811. )
  812. )
  813. ];
  814. }else{
  815. $image = [
  816. "ratio" => null,
  817. "url" => null
  818. ];
  819. }
  820. // get page type
  821. $pagetype_probe =
  822. $this->fuckhtml
  823. ->getElementsByTagName(
  824. "b"
  825. );
  826. $pagetype = "web";
  827. foreach($pagetype_probe as $probe){
  828. $pagetype =
  829. strtolower(
  830. trim(
  831. $this->fuckhtml
  832. ->getTextContent(
  833. $probe
  834. ),
  835. " 【】"
  836. )
  837. );
  838. }
  839. // get extra links
  840. $sublinks = [];
  841. foreach($div as $d){
  842. if(
  843. isset($d["attributes"]["class"]) &&
  844. strpos($d["attributes"]["class"], "exta-link") !== false
  845. ){
  846. $this->fuckhtml->load($d);
  847. $links =
  848. $this->fuckhtml
  849. ->getElementsByClassName(
  850. "cos-space-mt-xs",
  851. "div"
  852. );
  853. foreach($links as $link){
  854. $this->fuckhtml->load($link);
  855. $s_title =
  856. $this->fuckhtml
  857. ->getElementsByTagName(
  858. "h3"
  859. );
  860. if(count($s_title) === 0){
  861. // should not happen
  862. continue;
  863. }
  864. $data2 =
  865. json_decode(
  866. $this->fuckhtml
  867. ->getTextContent(
  868. $s_title[0]["attributes"]["data-click"]
  869. ),
  870. true
  871. );
  872. if(!isset($data2["clk_info"])){
  873. // wtf
  874. continue;
  875. }
  876. $data2 =
  877. json_decode(
  878. $data2["clk_info"],
  879. true
  880. );
  881. if(!isset($data2["url"])){
  882. // no link, fuck off
  883. continue;
  884. }
  885. $url =
  886. rawurldecode(
  887. $data2["url"]
  888. );
  889. $data =
  890. $this->fuckhtml
  891. ->getElementsByTagName(
  892. "p"
  893. );
  894. $s_description = null;
  895. if(count($data) !== 0){
  896. $data =
  897. json_decode(
  898. $this->fuckhtml
  899. ->getTextContent(
  900. $data[0]["attributes"]["sub-show-log"]
  901. ),
  902. true
  903. );
  904. if(isset($data["ext"]["content"])){
  905. $s_description = $data["ext"]["content"];
  906. }
  907. }
  908. $sublinks[] = [
  909. "title" =>
  910. $this->fuckhtml
  911. ->getTextContent(
  912. $s_title[0]
  913. ),
  914. "description" => $s_description,
  915. "url" => $url,
  916. "date" => null
  917. ];
  918. }
  919. break;
  920. }
  921. }
  922. $out["web"][] = [
  923. "title" => $title,
  924. "description" => $description,
  925. "url" =>
  926. $this->fuckhtml
  927. ->getTextContent(
  928. $datafield["attributes"]["mu"]
  929. ),
  930. "date" => $date,
  931. "type" => $pagetype,
  932. "thumb" => $image,
  933. "sublink" => $sublinks,
  934. "table" => []
  935. ];
  936. continue;
  937. }
  938. // parse special result
  939. $result =
  940. $this->fuckhtml
  941. ->getElementsByClassName(
  942. "result-op",
  943. [$datafield]
  944. );
  945. if(count($result) !== 0){
  946. //
  947. // Parse video carousel
  948. //
  949. if(
  950. isset($datafield["attributes"]["tpl"]) &&
  951. stripos($datafield["attributes"]["tpl"], "video") !== false
  952. ){
  953. preg_match(
  954. '/<!--s-data:([\S\s]*)-->/U',
  955. $datafield["innerHTML"],
  956. $matches
  957. );
  958. if(isset($matches[1])){
  959. $json =
  960. json_decode(
  961. $matches[1],
  962. true
  963. );
  964. if($json !== null){
  965. foreach($json["videoList"] as $video){
  966. $out["video"][] = [
  967. "title" => $video["title"],
  968. "description" =>
  969. $this->titledots(
  970. $video["desc"]
  971. ),
  972. "date" =>
  973. $this->parse_time(
  974. $video["pubTime"]
  975. ),
  976. "duration" =>
  977. $this->hms2int(
  978. $video["duration"]
  979. ),
  980. "views" =>
  981. $this->parse_viewcount(
  982. $video["playCount"]
  983. ),
  984. "thumb" => [
  985. "ratio" => "16:9",
  986. "url" => $video["poster"]
  987. ],
  988. "url" => $video["bindProps"]["link"]
  989. ];
  990. }
  991. }
  992. }
  993. continue;
  994. }
  995. //
  996. // Special result div (wiki entries, rich divs)
  997. //
  998. $title =
  999. $this->fuckhtml
  1000. ->getElementsByTagName(
  1001. "h3"
  1002. );
  1003. if(count($title) === 0){
  1004. // should have a title somewhere
  1005. continue;
  1006. }
  1007. $title =
  1008. explode(
  1009. ">",
  1010. $this->fuckhtml
  1011. ->getTextContent(
  1012. $title[0]
  1013. ),
  1014. 2
  1015. );
  1016. if(count($title) === 2){
  1017. $title = $title[1];
  1018. }else{
  1019. $title = $title[0];
  1020. }
  1021. // probe for wiki-like entry
  1022. $description =
  1023. $this->fuckhtml
  1024. ->getElementsByClassName(
  1025. "sc-paragraph",
  1026. "p"
  1027. );
  1028. if(count($description) === 0){
  1029. // try and get grey description
  1030. $description =
  1031. $this->fuckhtml
  1032. ->getElementsByClassName(
  1033. "c-color-gray2",
  1034. "p"
  1035. );
  1036. if(count($description) === 0){
  1037. // probe for special social media description
  1038. $description =
  1039. $this->fuckhtml
  1040. ->getElementsByClassName(
  1041. "c-color-text",
  1042. "div"
  1043. );
  1044. if(isset($description[0]["attributes"]["aria-label"])){
  1045. $description =
  1046. $this->fuckhtml
  1047. ->getTextContent(
  1048. $description[0]
  1049. ["attributes"]
  1050. ["aria-label"]
  1051. );
  1052. }else{
  1053. // check for news tab description
  1054. $span =
  1055. $this->fuckhtml
  1056. ->getElementsByClassName(
  1057. "c-font-normal",
  1058. "span"
  1059. );
  1060. $description = null;
  1061. foreach($span as $s){
  1062. if(isset($s["attributes"]["aria-label"])){
  1063. $description =
  1064. $this->titledots(
  1065. $this->fuckhtml
  1066. ->getTextContent(
  1067. $span[count($span) - 1]
  1068. )
  1069. );
  1070. break;
  1071. }
  1072. }
  1073. }
  1074. }else{
  1075. $description =
  1076. $this->fuckhtml
  1077. ->getTextContent(
  1078. $description[0]
  1079. );
  1080. }
  1081. }else{
  1082. preg_match(
  1083. '/<!--s-text-->([\S\s]*)<!--\/s-text-->/U',
  1084. $description[count($description) - 1]["innerHTML"],
  1085. $matches
  1086. );
  1087. if(isset($matches[1])){
  1088. $description =
  1089. $this->titledots(
  1090. $this->fuckhtml
  1091. ->getTextContent(
  1092. $matches[1]
  1093. )
  1094. );
  1095. }else{
  1096. $description = null;
  1097. }
  1098. }
  1099. // get thumbnail
  1100. $thumb =
  1101. $this->fuckhtml
  1102. ->getElementsByTagName(
  1103. "img"
  1104. );
  1105. if(count($thumb) !== 0){
  1106. $thumb = [
  1107. "ratio" => "1:1",
  1108. "url" =>
  1109. $this->unfuckthumb(
  1110. $this->fuckhtml
  1111. ->getTextContent(
  1112. $thumb[0]["attributes"]["src"]
  1113. )
  1114. )
  1115. ];
  1116. }else{
  1117. $thumb = [
  1118. "ratio" => null,
  1119. "url" => null
  1120. ];
  1121. }
  1122. // get sublinks
  1123. preg_match(
  1124. '/<!--s-data:([\S\s]*)-->/U',
  1125. $datafield["innerHTML"],
  1126. $matches
  1127. );
  1128. $sublinks = [];
  1129. if(isset($matches[1])){
  1130. $json =
  1131. json_decode(
  1132. $matches[1],
  1133. true
  1134. );
  1135. if($json !== null){
  1136. if(isset($json["buttons"])){
  1137. foreach($json["buttons"] as $button){
  1138. $sublinks[] = [
  1139. "title" => $button["text"],
  1140. "description" => null,
  1141. "date" => null,
  1142. "url" => $button["url"]
  1143. ];
  1144. }
  1145. }elseif(isset($json["mthreadList"])){
  1146. foreach($json["mthreadList"] as $thread){
  1147. $sublinks[] = [
  1148. "title" =>
  1149. $this->fuckhtml
  1150. ->getTextContent(
  1151. $thread["title"]
  1152. ),
  1153. "description" => null,
  1154. "date" => null,
  1155. "url" => $thread["ttsInfo"]["titleUrl"]
  1156. ];
  1157. }
  1158. }
  1159. }
  1160. }
  1161. // get URL
  1162. // handle http://fakeurl.baidu.com bullshit
  1163. $url =
  1164. $this->fuckhtml
  1165. ->getTextContent(
  1166. $datafield["attributes"]["mu"]
  1167. );
  1168. if(
  1169. preg_match(
  1170. '/^https?:\/\/(?:fakeurl|nourl)(?:\.ubs)?\.baidu\.com/',
  1171. $url
  1172. )
  1173. ){
  1174. // we got some bullshit, get jumpUrl instead
  1175. $as =
  1176. $this->fuckhtml
  1177. ->getElementsByTagName(
  1178. "a"
  1179. );
  1180. if(count($as) !== 0){
  1181. $url =
  1182. $this->fuckhtml
  1183. ->getTextContent(
  1184. $as[0]["attributes"]["href"]
  1185. );
  1186. }
  1187. }
  1188. // get xueshu sublinks
  1189. // get list
  1190. $xueshu_list =
  1191. $this->fuckhtml
  1192. ->getElementsByClassName(
  1193. "op-xueshu-links-d20-list",
  1194. $div
  1195. );
  1196. if(count($xueshu_list) !== 0){
  1197. $this->fuckhtml->load($xueshu_list[0]);
  1198. $rows =
  1199. $this->fuckhtml
  1200. ->getElementsByClassName(
  1201. "c-row",
  1202. "div"
  1203. );
  1204. // remove "read more" bullshit
  1205. foreach($rows as $row){
  1206. if(strpos($row["attributes"]["class"], "op-xueshu-links-more") !== false){
  1207. $xueshu_list[0]["innerHTML"] =
  1208. str_replace(
  1209. $row["outerHTML"],
  1210. "",
  1211. $xueshu_list[0]["innerHTML"]
  1212. );
  1213. }
  1214. }
  1215. $this->fuckhtml->load($xueshu_list[0]);
  1216. foreach($rows as $row){
  1217. $this->fuckhtml->load($row);
  1218. if(strpos($row["attributes"]["class"], "op-xueshu-links-more") !== false){
  1219. continue;
  1220. }
  1221. $as =
  1222. $this->fuckhtml
  1223. ->getElementsByTagName(
  1224. "a"
  1225. );
  1226. foreach($as as $a){
  1227. $sublinks[] = [
  1228. "title" =>
  1229. $this->titledots(
  1230. $this->fuckhtml
  1231. ->getTextContent(
  1232. $a
  1233. )
  1234. ),
  1235. "description" => null,
  1236. "date" => null,
  1237. "url" =>
  1238. $this->fuckhtml
  1239. ->getTextContent(
  1240. $a["attributes"]["href"]
  1241. )
  1242. ];
  1243. }
  1244. }
  1245. }
  1246. $out["web"][] = [
  1247. "title" => $title,
  1248. "description" => $description,
  1249. "url" => $url,
  1250. "date" => null,
  1251. "type" => "web",
  1252. "thumb" => $thumb,
  1253. "sublink" => $sublinks,
  1254. "table" => []
  1255. ];
  1256. continue;
  1257. }
  1258. }
  1259. //
  1260. // Remove tracking URLs and fetch additonal image resources
  1261. //
  1262. $this->resolve_urls($proxy, $out, ["web", "video"]);
  1263. $this->resolve_images($proxy, $out);
  1264. return $out;
  1265. }
  1266. public function image($get){
  1267. // https://image.baidu.com/search/acjson?word=asmr&rn=60&pn=0&newReq=1
  1268. //$json = file_get_contents("scraper/baidu_img.json");
  1269. if($get["npt"]){
  1270. [$params, $proxy] = $this->backend->get($get["npt"], "images");
  1271. $params = json_decode($params, true);
  1272. $params["pn"] = $params["pn"] + 60;
  1273. }else{
  1274. $proxy = $this->backend->get_ip();
  1275. $params = [
  1276. "word" => $get["s"],
  1277. "rn" => 60, // results/page
  1278. "pn" => 0, // item increment (0 * 60)
  1279. "newReq" => 1 // otherwise json is fucked up
  1280. ];
  1281. switch($get["sort"]){
  1282. case "latest": $params["latest"] = 1; break;
  1283. case "hot": $params["hot"] = 1; break;
  1284. }
  1285. if($get["size"] != "any"){
  1286. $params["z"] = $get["size"];
  1287. }
  1288. if($get["ratio"] != "any"){
  1289. $params["imgratio"] = $get["ratio"];
  1290. }
  1291. if($get["format"] != "any"){
  1292. $params["imgformat"] = $get["format"];
  1293. }
  1294. if($get["color"] != "any"){
  1295. $params["ic"] = $get["color"];
  1296. }
  1297. switch($get["type"]){
  1298. case "hd": $params["hd"] = 1; break;
  1299. case "isImgSet": $params["isImgSet"] = 1; break;
  1300. case "copyright": $params["copyright"] = 1; break;
  1301. }
  1302. }
  1303. try{
  1304. $json =
  1305. $this->get(
  1306. $proxy,
  1307. "https://image.baidu.com/search/acjson",
  1308. $params,
  1309. "https://image.baidu.com/search/index?tn=baiduimage&word=" . urlencode($get["s"])
  1310. );
  1311. }catch(Exception $error){
  1312. throw new Exception("Failed to fetch JSON");
  1313. }
  1314. $json = json_decode($json, true);
  1315. if($json === null){
  1316. // detect captcha first
  1317. $this->fuckhtml->load($json);
  1318. $this->detect_ass();
  1319. // fallback to json decode error
  1320. throw new Exception("Failed to decode JSON");
  1321. }
  1322. if(
  1323. isset($json["message"]) &&
  1324. $json["message"] != "success"
  1325. ){
  1326. throw new Exception("Baidu returned an error: {$json["message"]}");
  1327. }
  1328. if(!isset($json["data"]["images"])){
  1329. throw new Exception("Baidu did not return an image object");
  1330. }
  1331. $out = [
  1332. "status" => "ok",
  1333. "npt" => null,
  1334. "image" => []
  1335. ];
  1336. foreach($json["data"]["images"] as $image){
  1337. parse_str(parse_url($image["thumburl"], PHP_URL_QUERY), $thumb_size);
  1338. $out["image"][] = [
  1339. "title" =>
  1340. $this->fuckhtml
  1341. ->getTextContent(
  1342. $image["titleShow"]
  1343. ),
  1344. "source" => [
  1345. [
  1346. "url" => $image["objurl"],
  1347. "width" => (int)$image["width"],
  1348. "height" => (int)$image["height"]
  1349. ],
  1350. [ // thumbnail
  1351. "url" => $image["thumburl"],
  1352. "width" => (int)$thumb_size["w"],
  1353. "height" => (int)$thumb_size["h"]
  1354. ]
  1355. ],
  1356. "url" => $image["fromUrl"]
  1357. ];
  1358. }
  1359. //
  1360. // Detect if there's a next page
  1361. //
  1362. if((int)$json["data"]["totalNum"] >= $params["pn"] + 60){
  1363. $out["npt"] =
  1364. $this->backend->store(
  1365. json_encode($params),
  1366. "images",
  1367. $proxy
  1368. );
  1369. }
  1370. return $out;
  1371. }
  1372. public function video($get){
  1373. // https://www.baidu.com/sf/vsearch?pd=video&tn=vsearch&wd=jak%2Band%2Bdaxter&async=1&pn=0
  1374. // increase &pn +20 for pagination
  1375. //$html = file_get_contents("scraper/baidu_vid.html");
  1376. if($get["npt"]){
  1377. [$params, $proxy] = $this->backend->get($get["npt"], "videos");
  1378. $params = json_decode($params, true);
  1379. $params["pn"] = $params["pn"] + 10;
  1380. }else{
  1381. $proxy = $this->backend->get_ip();
  1382. $params = [
  1383. "pd" => "video",
  1384. "tn" => "vsearch",
  1385. "wd" => $get["s"],
  1386. "async" => 1,
  1387. "pn" => 0
  1388. ];
  1389. }
  1390. try{
  1391. $html =
  1392. $this->get(
  1393. $proxy,
  1394. "https://www.baidu.com/sf/vsearch",
  1395. $params
  1396. );
  1397. }catch(Exception $error){
  1398. throw new Exception("Failed to get search page");
  1399. }
  1400. $html =
  1401. str_replace(
  1402. ["\r", "\n"],
  1403. "",
  1404. $html
  1405. );
  1406. $out = [
  1407. "status" => "ok",
  1408. "npt" => null,
  1409. "video" => [],
  1410. "author" => [],
  1411. "livestream" => [],
  1412. "playlist" => [],
  1413. "reel" => []
  1414. ];
  1415. $html = explode("<script>", $html);
  1416. foreach($html as $result){
  1417. $result = trim($result);
  1418. $this->fuckhtml->load($result);
  1419. // get URL
  1420. preg_match(
  1421. '/<!-- *([^ ]*) *-->/',
  1422. $result,
  1423. $matches
  1424. );
  1425. if(!isset($matches[1])){
  1426. // no link, give up
  1427. continue;
  1428. }
  1429. $link = $matches[1];
  1430. // get title
  1431. $title =
  1432. $this->fuckhtml
  1433. ->getElementsByClassName(
  1434. "video-title",
  1435. "a"
  1436. );
  1437. if(count($title) === 0){
  1438. // should not happen
  1439. continue;
  1440. }
  1441. $title =
  1442. $this->fuckhtml
  1443. ->getTextContent(
  1444. $title[0]
  1445. );
  1446. // get thumbnail
  1447. $img =
  1448. $this->fuckhtml
  1449. ->getElementsByClassName(
  1450. "border-radius",
  1451. "img"
  1452. );
  1453. if(count($img) !== 0){
  1454. $thumb = [
  1455. "url" =>
  1456. $this->unfuckthumb(
  1457. $this->fuckhtml
  1458. ->getTextContent(
  1459. $img[0]["attributes"]["src"]
  1460. )
  1461. ),
  1462. "ratio" => "16:9"
  1463. ];
  1464. }else{
  1465. $thumb = [
  1466. "url" => null,
  1467. "ratio" => null
  1468. ];
  1469. }
  1470. $span =
  1471. $this->fuckhtml
  1472. ->getElementsByTagName(
  1473. "span"
  1474. );
  1475. // get duration
  1476. $duration =
  1477. $this->fuckhtml
  1478. ->getElementsByClassName(
  1479. "video_play_timer",
  1480. $span
  1481. );
  1482. if(count($duration) !== 0){
  1483. $duration =
  1484. $this->hms2int(
  1485. $this->fuckhtml
  1486. ->getTextContent(
  1487. $duration[0]
  1488. )
  1489. );
  1490. }else{
  1491. $duration = null;
  1492. }
  1493. // get author
  1494. // 来源:哔哩哔哩
  1495. $author =
  1496. $this->fuckhtml
  1497. ->getElementsByClassName(
  1498. "wetSource",
  1499. $span
  1500. );
  1501. if(count($author) !== 0){
  1502. $author =
  1503. explode(
  1504. ":",
  1505. $this->fuckhtml
  1506. ->getTextContent(
  1507. $author[0]
  1508. ),
  1509. 2
  1510. )[1];
  1511. }else{
  1512. $author = null;
  1513. }
  1514. // get date posted
  1515. //发布时间:2024-05-06
  1516. // AND get description
  1517. // 简介:Our first look
  1518. $infospans =
  1519. array_merge(
  1520. $this->fuckhtml
  1521. ->getElementsByClassName(
  1522. "c-font-normal",
  1523. $span
  1524. ),
  1525. $this->fuckhtml
  1526. ->getElementsByClassName(
  1527. "c-font-normal",
  1528. "div"
  1529. )
  1530. );
  1531. $date = null;
  1532. $description = null;
  1533. foreach($infospans as $infospan){
  1534. $infospan =
  1535. explode(
  1536. ":",
  1537. $this->fuckhtml
  1538. ->getTextContent(
  1539. $infospan
  1540. ),
  1541. 2
  1542. );
  1543. if(count($infospan) !== 2){
  1544. // should not happen
  1545. continue;
  1546. }
  1547. $infospan[1] =
  1548. $this->fuckhtml
  1549. ->getTextContent(
  1550. $infospan[1]
  1551. );
  1552. switch($infospan[0]){
  1553. case "发布时间": // date posted
  1554. $date = $this->parse_time($infospan[1]);
  1555. break;
  1556. case "简介": // description
  1557. $description = $infospan[1];
  1558. break;
  1559. }
  1560. }
  1561. $out["video"][] = [
  1562. "title" => $this->titledots($title),
  1563. "description" => $this->titledots($description),
  1564. "author" => [
  1565. "name" => $author,
  1566. "url" => null,
  1567. "avatar" => null
  1568. ],
  1569. "date" => $date,
  1570. "duration" => $duration,
  1571. "views" => null,
  1572. "thumb" => $thumb,
  1573. "url" => $link
  1574. ];
  1575. }
  1576. if(count($out["video"]) === 10){
  1577. // assume there's another page after this
  1578. $out["npt"] =
  1579. $this->backend->store(
  1580. json_encode($params),
  1581. "videos",
  1582. $proxy
  1583. );
  1584. }
  1585. return $out;
  1586. }
  1587. public function news($get){
  1588. //$proxy = $this->backend->get_ip();
  1589. //$html = file_get_contents("scraper/baidu.html");
  1590. //$npt_data = [];
  1591. if($get["npt"]){
  1592. [$json, $proxy] = $this->backend->get($get["npt"], "news");
  1593. $json = json_decode($json, true);
  1594. $this->cookie = $json["cookie"];
  1595. $npt_data = $json["req"];
  1596. $npt_data["pn"] = $npt_data["pn"] + 20;
  1597. try{
  1598. $html = $this->get(
  1599. $proxy,
  1600. "https://www.baidu.com/s",
  1601. $npt_data
  1602. );
  1603. }catch(Exception $error){
  1604. throw new Exception("Failed to fetch search page");
  1605. }
  1606. }else{
  1607. //
  1608. // Get authentication token
  1609. //
  1610. $proxy = $this->backend->get_ip();
  1611. $npt_data = [
  1612. "wd" => $get["s"],
  1613. "rn" => 20,
  1614. "tn" => "news"
  1615. ];
  1616. // @TODO add filters
  1617. try{
  1618. $html = $this->get(
  1619. $proxy,
  1620. "https://www.baidu.com/s",
  1621. $npt_data
  1622. );
  1623. }catch(Exception $error){
  1624. throw new Exception("Failed to fetch search page");
  1625. }
  1626. $npt_data["pn"] = 0;
  1627. }
  1628. $data = $this->parse_search($proxy, "news", $npt_data, $html);
  1629. $out = [
  1630. "status" => "ok",
  1631. "npt" => $data["npt"],
  1632. "news" => []
  1633. ];
  1634. foreach($data["web"] as $article){
  1635. $out["news"][] = [
  1636. "title" => $article["title"],
  1637. "author" => null,
  1638. "description" => $article["description"],
  1639. "date" => $article["date"],
  1640. "thumb" => [
  1641. "url" => $article["thumb"]["url"],
  1642. "ratio" => $article["thumb"]["url"] !== null ? "16:9" : null,
  1643. ],
  1644. "url" => $article["url"]
  1645. ];
  1646. }
  1647. return $out;
  1648. }
  1649. private function unfuckthumb($url){
  1650. // probe for proxy URL
  1651. $parsed_url = parse_url($url);
  1652. if(
  1653. preg_match(
  1654. '/^https?:\/\/gimg(?:[0-9]+)?\.baidu\.com/',
  1655. $url
  1656. )
  1657. ){
  1658. $parts = explode("src=", $url);
  1659. if(count($parts) !== 2){
  1660. // shits fucked
  1661. return $url;
  1662. }
  1663. return urldecode(explode("&", $parts[1])[0]);
  1664. }
  1665. $q = explode("&", $url, 2);
  1666. if(count($q) !== 2){
  1667. // shits fucked, again
  1668. return $url;
  1669. }
  1670. // baidu devs are fucking retarded and dont follow spec:
  1671. // &fmt=auto?s=BB32F3A050471AEC72886934030090C4&sec=1753203600&t=0fb2194775d3bd3d1bb114b818479e0a
  1672. parse_str(str_replace("?", "&", $q[1]), $query);
  1673. if(isset($query["size"])){ unset($query["size"]); }
  1674. if(isset($query["q"])){ $query["q"] = "90"; }
  1675. $query = http_build_query($query);
  1676. return
  1677. str_replace(
  1678. $q[1],
  1679. $query,
  1680. $url
  1681. );
  1682. }
  1683. private function titledots($title){
  1684. return trim($title, " .\t\n\r\0\x0B…");
  1685. }
  1686. private function hms2int($time){
  1687. $parts = explode(":", $time, 3);
  1688. $time = 0;
  1689. if(count($parts) === 3){
  1690. // hours
  1691. $time = $time + ((int)$parts[0] * 3600);
  1692. array_shift($parts);
  1693. }
  1694. if(count($parts) === 2){
  1695. // minutes
  1696. $time = $time + ((int)$parts[0] * 60);
  1697. array_shift($parts);
  1698. }
  1699. // seconds
  1700. $time = $time + (int)$parts[0];
  1701. return $time;
  1702. }
  1703. private function parse_viewcount($views){
  1704. if(
  1705. // 10k (wtf lol)
  1706. preg_match(
  1707. '/([0-9]+)万次/',
  1708. $views,
  1709. $matches
  1710. )
  1711. ){
  1712. return (int)$matches[1] * 10000;
  1713. }
  1714. if(
  1715. // units
  1716. preg_match(
  1717. '/([0-9]+)次/',
  1718. $views,
  1719. $matches
  1720. )
  1721. ){
  1722. return (int)$matches[1];
  1723. }
  1724. return null;
  1725. }
  1726. private function parse_time($time){
  1727. // 2023年8月7日 => yyyy/m/d
  1728. if(
  1729. preg_match(
  1730. '/([0-9]{4})年([0-9]{1,2})月([0-9]{1,2})日/',
  1731. $time,
  1732. $matches
  1733. )
  1734. ){
  1735. return strtotime("{$matches[1]}/{$matches[2]}/{$matches[3]}");
  1736. }
  1737. // 昨天11:45 => yesterday at 11:45
  1738. // 昨天 => yesterday
  1739. if(
  1740. preg_match(
  1741. '/昨天(.*)/',
  1742. $time,
  1743. $matches
  1744. )
  1745. ){
  1746. return strtotime("Yesterday {$matches[1]}");
  1747. }
  1748. // 3天前 => 3 days ago
  1749. if(
  1750. preg_match(
  1751. '/([0-9]{1,4})天前/',
  1752. $time,
  1753. $matches
  1754. )
  1755. ){
  1756. return strtotime("{$matches[1]} days ago");
  1757. }
  1758. // 1个月前 => 1 month ago
  1759. if(
  1760. preg_match(
  1761. '/([0-9]{1,4})个月前/',
  1762. $time,
  1763. $matches
  1764. )
  1765. ){
  1766. return strtotime("{$matches[1]} months ago");
  1767. }
  1768. // attempt to parse as-is
  1769. $time = strtotime($time);
  1770. if($time !== false){
  1771. return $time;
  1772. }
  1773. return null;
  1774. }
  1775. private function detect_ass(){
  1776. $as =
  1777. $this->fuckhtml
  1778. ->getElementsByTagName(
  1779. "a"
  1780. );
  1781. if(
  1782. count($as) === 0 ||
  1783. preg_match(
  1784. '/^https?:\/\/wappass\.baidu\.com\/static\/captcha/',
  1785. $this->fuckhtml
  1786. ->getTextContent(
  1787. $as[0]["attributes"]["href"]
  1788. )
  1789. )
  1790. ){
  1791. throw new Exception("Baidu returned a Captcha");
  1792. }
  1793. }
  1794. }