1
0

baidu.php 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229
  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. $card =
  537. $this->fuckhtml
  538. ->getElementsByClassName(
  539. "cosc-card",
  540. $div
  541. );
  542. if(count($card) !== 0){
  543. //
  544. // Parse chinese youtube shorts
  545. //
  546. $ytshorts_probe =
  547. $this->fuckhtml
  548. ->getElementsByClassName(
  549. "tts-b-item",
  550. $div
  551. );
  552. if(count($ytshorts_probe) !== 0){
  553. $videos =
  554. $this->fuckhtml
  555. ->getElementsByAttributeValue(
  556. "data-show",
  557. "list",
  558. $div
  559. );
  560. foreach($videos as $video){
  561. $this->fuckhtml->load($video);
  562. $title =
  563. $this->fuckhtml
  564. ->getElementsByClassName(
  565. "cosc-title-slot",
  566. "span"
  567. );
  568. if(count($title) === 0){
  569. continue;
  570. }
  571. $url =
  572. $this->fuckhtml
  573. ->getElementsByTagName(
  574. "a"
  575. );
  576. if(count($url) === 0){
  577. continue;
  578. }
  579. $image =
  580. $this->fuckhtml
  581. ->getElementsByClassName(
  582. "cos-image-body",
  583. "img"
  584. );
  585. if(count($image) === 0){
  586. $image = [
  587. "ratio" => null,
  588. "url" => null
  589. ];
  590. }else{
  591. $image = [
  592. "ratio" => "1:1",
  593. "url" =>
  594. $this->fuckhtml
  595. ->getTextContent(
  596. $image[0]["attributes"]["src"]
  597. )
  598. ];
  599. }
  600. // get duration
  601. $divs =
  602. $this->fuckhtml
  603. ->getElementsByAttributeName(
  604. "class",
  605. "div"
  606. );
  607. $duration = null;
  608. foreach($divs as $probe){
  609. if(strpos($probe["attributes"]["class"], "tag-bottom-right") !== false){
  610. $duration =
  611. $this->hms2int(
  612. $this->fuckhtml
  613. ->getTextContent(
  614. $probe
  615. )
  616. );
  617. break;
  618. }
  619. }
  620. $out["video"][] = [
  621. "title" =>
  622. $this->fuckhtml
  623. ->getTextContent(
  624. $title[0]
  625. ),
  626. "description" => null,
  627. "date" => null,
  628. "duration" => $duration,
  629. "views" => null,
  630. "thumb" => $image,
  631. "url" =>
  632. $this->fuckhtml
  633. ->getTextContent(
  634. $url[0]["attributes"]["href"]
  635. )
  636. ];
  637. }
  638. }
  639. //
  640. // Parse image carousel
  641. //
  642. $is_image_carousel = false;
  643. foreach($div as $d){
  644. if(
  645. isset($d["attributes"]["class"]) &&
  646. strpos($d["attributes"]["class"], "image-container") !== false
  647. ){
  648. $is_image_carousel = true;
  649. break;
  650. }
  651. }
  652. if($is_image_carousel){
  653. preg_match(
  654. '/<!--s-data:([\S\s]*)-->/U',
  655. $datafield["innerHTML"],
  656. $matches
  657. );
  658. if(isset($matches[1])){
  659. // weird behavior with the smaller image carousel where --cos* CSS variables are escaped wrong
  660. $json =
  661. $this->fuckhtml
  662. ->parseJsObject(
  663. str_replace(
  664. "-\-",
  665. "--",
  666. $matches[1]
  667. )
  668. );
  669. if(
  670. $json !== null &&
  671. isset($json["imageList"][0]["images"])
  672. ){
  673. // parse image carousel
  674. foreach($json["imageList"][0]["images"] as $image){
  675. parse_str(parse_url($image["thumburl"], PHP_URL_QUERY), $thumb_size);
  676. $out["image"][] = [
  677. "title" => "image",
  678. "source" => [
  679. [
  680. "url" => $image["objurl"],
  681. "width" => (int)$image["width"],
  682. "height" => (int)$image["height"]
  683. ],
  684. [ // thumbnail
  685. "url" => $image["thumburl"],
  686. "width" => (int)$thumb_size["w"],
  687. "height" => (int)$thumb_size["h"]
  688. ]
  689. ],
  690. "url" => $image["jumpUrl"]
  691. ];
  692. }
  693. }
  694. }
  695. }
  696. continue;
  697. }
  698. if(!isset($datafield["attributes"]["mu"])){
  699. // dont scrape if we dont have the direct link
  700. continue;
  701. }
  702. // class:FYB_RD -> News garbage, IGNORE
  703. $result =
  704. $this->fuckhtml
  705. ->getElementsByClassName(
  706. "result",
  707. [$datafield]
  708. );
  709. if(count($result) !== 0){
  710. //
  711. // Parse normal search result
  712. //
  713. $title =
  714. $this->fuckhtml
  715. ->getElementsByClassName(
  716. "sc-link",
  717. "a"
  718. );
  719. if(count($title) === 0){
  720. // should not happen
  721. continue;
  722. }
  723. $title =
  724. $this->titledots(
  725. $this->fuckhtml
  726. ->getTextContent(
  727. $title[0]
  728. )
  729. );
  730. $description =
  731. $this->fuckhtml
  732. ->getElementsByClassName(
  733. "c-color",
  734. $div
  735. );
  736. if(count($description) !== 0){
  737. $this->fuckhtml->load($description[0]);
  738. $description =
  739. $this->fuckhtml
  740. ->getElementsByAttributeName(
  741. "class",
  742. "span"
  743. );
  744. $found_desc = false;
  745. foreach($description as $desc){
  746. if(stripos($desc["attributes"]["class"], "summary-text") !== false){
  747. $found_desc = true;
  748. $description =
  749. $this->titledots(
  750. $this->fuckhtml
  751. ->getTextContent(
  752. $desc
  753. )
  754. );
  755. break;
  756. }
  757. }
  758. if($found_desc === false){
  759. $description = null;
  760. }
  761. $this->fuckhtml->load($datafield);
  762. }else{
  763. $description = null;
  764. }
  765. // parse date
  766. $date_probe =
  767. $this->fuckhtml
  768. ->getElementsByClassName(
  769. "cos-color-text-minor",
  770. "span"
  771. );
  772. if(count($date_probe) !== 0){
  773. $date =
  774. $this->parse_time(
  775. $this->fuckhtml
  776. ->getTextContent(
  777. $date_probe[0]
  778. )
  779. );
  780. }else{
  781. $date = null;
  782. }
  783. // parse image
  784. $img =
  785. $this->fuckhtml
  786. ->getElementsByTagName(
  787. "img"
  788. );
  789. if(count($img) !== 0){
  790. $image = [
  791. "ratio" => "16:9",
  792. "url" =>
  793. $this->unfuckthumb(
  794. $this->fuckhtml
  795. ->getTextContent(
  796. $img[0]["attributes"]["src"]
  797. )
  798. )
  799. ];
  800. }else{
  801. $image = [
  802. "ratio" => null,
  803. "url" => null
  804. ];
  805. }
  806. // get page type
  807. $pagetype_probe =
  808. $this->fuckhtml
  809. ->getElementsByTagName(
  810. "b"
  811. );
  812. $pagetype = "web";
  813. foreach($pagetype_probe as $probe){
  814. $pagetype =
  815. strtolower(
  816. trim(
  817. $this->fuckhtml
  818. ->getTextContent(
  819. $probe
  820. ),
  821. " 【】"
  822. )
  823. );
  824. }
  825. // get extra links
  826. $sublinks = [];
  827. foreach($div as $d){
  828. if(
  829. isset($d["attributes"]["class"]) &&
  830. strpos($d["attributes"]["class"], "exta-link") !== false
  831. ){
  832. $this->fuckhtml->load($d);
  833. $links =
  834. $this->fuckhtml
  835. ->getElementsByClassName(
  836. "cos-space-mt-xs",
  837. "div"
  838. );
  839. foreach($links as $link){
  840. $this->fuckhtml->load($link);
  841. $s_title =
  842. $this->fuckhtml
  843. ->getElementsByTagName(
  844. "h3"
  845. );
  846. if(count($s_title) === 0){
  847. // should not happen
  848. continue;
  849. }
  850. $data2 =
  851. json_decode(
  852. $this->fuckhtml
  853. ->getTextContent(
  854. $s_title[0]["attributes"]["data-click"]
  855. ),
  856. true
  857. );
  858. if(!isset($data2["clk_info"])){
  859. // wtf
  860. continue;
  861. }
  862. $data2 =
  863. json_decode(
  864. $data2["clk_info"],
  865. true
  866. );
  867. if(!isset($data2["url"])){
  868. // no link, fuck off
  869. continue;
  870. }
  871. $url =
  872. rawurldecode(
  873. $data2["url"]
  874. );
  875. $data =
  876. $this->fuckhtml
  877. ->getElementsByTagName(
  878. "p"
  879. );
  880. $s_description = null;
  881. if(count($data) !== 0){
  882. $data =
  883. json_decode(
  884. $this->fuckhtml
  885. ->getTextContent(
  886. $data[0]["attributes"]["sub-show-log"]
  887. ),
  888. true
  889. );
  890. if(isset($data["ext"]["content"])){
  891. $s_description = $data["ext"]["content"];
  892. }
  893. }
  894. $sublinks[] = [
  895. "title" =>
  896. $this->fuckhtml
  897. ->getTextContent(
  898. $s_title[0]
  899. ),
  900. "description" => $s_description,
  901. "url" => $url,
  902. "date" => null
  903. ];
  904. }
  905. break;
  906. }
  907. }
  908. $out["web"][] = [
  909. "title" => $title,
  910. "description" => $description,
  911. "url" =>
  912. $this->fuckhtml
  913. ->getTextContent(
  914. $datafield["attributes"]["mu"]
  915. ),
  916. "date" => $date,
  917. "type" => $pagetype,
  918. "thumb" => $image,
  919. "sublink" => $sublinks,
  920. "table" => []
  921. ];
  922. continue;
  923. }
  924. // parse special result
  925. $result =
  926. $this->fuckhtml
  927. ->getElementsByClassName(
  928. "result-op",
  929. [$datafield]
  930. );
  931. if(count($result) !== 0){
  932. //
  933. // Parse video carousel
  934. //
  935. if(
  936. isset($datafield["attributes"]["tpl"]) &&
  937. stripos($datafield["attributes"]["tpl"], "video") !== false
  938. ){
  939. preg_match(
  940. '/<!--s-data:([\S\s]*)-->/U',
  941. $datafield["innerHTML"],
  942. $matches
  943. );
  944. if(isset($matches[1])){
  945. $json =
  946. json_decode(
  947. $matches[1],
  948. true
  949. );
  950. if($json !== null){
  951. foreach($json["videoList"] as $video){
  952. $out["video"][] = [
  953. "title" => $video["title"],
  954. "description" =>
  955. $this->titledots(
  956. $video["desc"]
  957. ),
  958. "date" =>
  959. $this->parse_time(
  960. $video["pubTime"]
  961. ),
  962. "duration" =>
  963. $this->hms2int(
  964. $video["duration"]
  965. ),
  966. "views" =>
  967. $this->parse_viewcount(
  968. $video["playCount"]
  969. ),
  970. "thumb" => [
  971. "ratio" => "16:9",
  972. "url" => $video["poster"]
  973. ],
  974. "url" => $video["bindProps"]["link"]
  975. ];
  976. }
  977. }
  978. }
  979. continue;
  980. }
  981. //
  982. // Special result div (wiki entries, rich divs)
  983. //
  984. $title =
  985. $this->fuckhtml
  986. ->getElementsByTagName(
  987. "h3"
  988. );
  989. if(count($title) === 0){
  990. // should have a title somewhere
  991. continue;
  992. }
  993. $title =
  994. explode(
  995. ">",
  996. $this->fuckhtml
  997. ->getTextContent(
  998. $title[0]
  999. ),
  1000. 2
  1001. );
  1002. if(count($title) === 2){
  1003. $title = $title[1];
  1004. }else{
  1005. $title = $title[0];
  1006. }
  1007. // probe for wiki-like entry
  1008. $description =
  1009. $this->fuckhtml
  1010. ->getElementsByClassName(
  1011. "sc-paragraph",
  1012. "p"
  1013. );
  1014. if(count($description) === 0){
  1015. // try and get grey description
  1016. $description =
  1017. $this->fuckhtml
  1018. ->getElementsByClassName(
  1019. "c-color-gray2",
  1020. "p"
  1021. );
  1022. if(count($description) === 0){
  1023. // probe for special social media description
  1024. $description =
  1025. $this->fuckhtml
  1026. ->getElementsByClassName(
  1027. "c-color-text",
  1028. "div"
  1029. );
  1030. if(isset($description[0]["attributes"]["aria-label"])){
  1031. $description =
  1032. $this->fuckhtml
  1033. ->getTextContent(
  1034. $description[0]
  1035. ["attributes"]
  1036. ["aria-label"]
  1037. );
  1038. }else{
  1039. // check for news tab description
  1040. $span =
  1041. $this->fuckhtml
  1042. ->getElementsByClassName(
  1043. "c-font-normal",
  1044. "span"
  1045. );
  1046. $description = null;
  1047. foreach($span as $s){
  1048. if(isset($s["attributes"]["aria-label"])){
  1049. $description =
  1050. $this->titledots(
  1051. $this->fuckhtml
  1052. ->getTextContent(
  1053. $span[count($span) - 1]
  1054. )
  1055. );
  1056. break;
  1057. }
  1058. }
  1059. }
  1060. }else{
  1061. $description =
  1062. $this->fuckhtml
  1063. ->getTextContent(
  1064. $description[0]
  1065. );
  1066. }
  1067. }else{
  1068. preg_match(
  1069. '/<!--s-text-->([\S\s]*)<!--\/s-text-->/U',
  1070. $description[count($description) - 1]["innerHTML"],
  1071. $matches
  1072. );
  1073. if(isset($matches[1])){
  1074. $description =
  1075. $this->titledots(
  1076. $this->fuckhtml
  1077. ->getTextContent(
  1078. $matches[1]
  1079. )
  1080. );
  1081. }else{
  1082. $description = null;
  1083. }
  1084. }
  1085. // get thumbnail
  1086. $thumb =
  1087. $this->fuckhtml
  1088. ->getElementsByTagName(
  1089. "img"
  1090. );
  1091. if(count($thumb) !== 0){
  1092. $thumb = [
  1093. "ratio" => "1:1",
  1094. "url" =>
  1095. $this->unfuckthumb(
  1096. $this->fuckhtml
  1097. ->getTextContent(
  1098. $thumb[0]["attributes"]["src"]
  1099. )
  1100. )
  1101. ];
  1102. }else{
  1103. $thumb = [
  1104. "ratio" => null,
  1105. "url" => null
  1106. ];
  1107. }
  1108. // get sublinks
  1109. preg_match(
  1110. '/<!--s-data:([\S\s]*)-->/U',
  1111. $datafield["innerHTML"],
  1112. $matches
  1113. );
  1114. $sublinks = [];
  1115. if(isset($matches[1])){
  1116. $json =
  1117. json_decode(
  1118. $matches[1],
  1119. true
  1120. );
  1121. if($json !== null){
  1122. if(isset($json["buttons"])){
  1123. foreach($json["buttons"] as $button){
  1124. $sublinks[] = [
  1125. "title" => $button["text"],
  1126. "description" => null,
  1127. "date" => null,
  1128. "url" => $button["url"]
  1129. ];
  1130. }
  1131. }elseif(isset($json["mthreadList"])){
  1132. foreach($json["mthreadList"] as $thread){
  1133. $sublinks[] = [
  1134. "title" =>
  1135. $this->fuckhtml
  1136. ->getTextContent(
  1137. $thread["title"]
  1138. ),
  1139. "description" => null,
  1140. "date" => null,
  1141. "url" => $thread["ttsInfo"]["titleUrl"]
  1142. ];
  1143. }
  1144. }
  1145. }
  1146. }
  1147. // get URL
  1148. // handle http://fakeurl.baidu.com bullshit
  1149. $url =
  1150. $this->fuckhtml
  1151. ->getTextContent(
  1152. $datafield["attributes"]["mu"]
  1153. );
  1154. if(
  1155. preg_match(
  1156. '/^https?:\/\/(?:fakeurl|nourl)(?:\.ubs)?\.baidu\.com/',
  1157. $url
  1158. )
  1159. ){
  1160. // we got some bullshit, get jumpUrl instead
  1161. $as =
  1162. $this->fuckhtml
  1163. ->getElementsByTagName(
  1164. "a"
  1165. );
  1166. if(count($as) !== 0){
  1167. $url =
  1168. $this->fuckhtml
  1169. ->getTextContent(
  1170. $as[0]["attributes"]["href"]
  1171. );
  1172. }
  1173. }
  1174. // get xueshu sublinks
  1175. // get list
  1176. $xueshu_list =
  1177. $this->fuckhtml
  1178. ->getElementsByClassName(
  1179. "op-xueshu-links-d20-list",
  1180. $div
  1181. );
  1182. if(count($xueshu_list) !== 0){
  1183. $this->fuckhtml->load($xueshu_list[0]);
  1184. $rows =
  1185. $this->fuckhtml
  1186. ->getElementsByClassName(
  1187. "c-row",
  1188. "div"
  1189. );
  1190. // remove "read more" bullshit
  1191. foreach($rows as $row){
  1192. if(strpos($row["attributes"]["class"], "op-xueshu-links-more") !== false){
  1193. $xueshu_list[0]["innerHTML"] =
  1194. str_replace(
  1195. $row["outerHTML"],
  1196. "",
  1197. $xueshu_list[0]["innerHTML"]
  1198. );
  1199. }
  1200. }
  1201. $this->fuckhtml->load($xueshu_list[0]);
  1202. foreach($rows as $row){
  1203. $this->fuckhtml->load($row);
  1204. if(strpos($row["attributes"]["class"], "op-xueshu-links-more") !== false){
  1205. continue;
  1206. }
  1207. $as =
  1208. $this->fuckhtml
  1209. ->getElementsByTagName(
  1210. "a"
  1211. );
  1212. foreach($as as $a){
  1213. $sublinks[] = [
  1214. "title" =>
  1215. $this->titledots(
  1216. $this->fuckhtml
  1217. ->getTextContent(
  1218. $a
  1219. )
  1220. ),
  1221. "description" => null,
  1222. "date" => null,
  1223. "url" =>
  1224. $this->fuckhtml
  1225. ->getTextContent(
  1226. $a["attributes"]["href"]
  1227. )
  1228. ];
  1229. }
  1230. }
  1231. }
  1232. $out["web"][] = [
  1233. "title" => $title,
  1234. "description" => $description,
  1235. "url" => $url,
  1236. "date" => null,
  1237. "type" => "web",
  1238. "thumb" => $thumb,
  1239. "sublink" => $sublinks,
  1240. "table" => []
  1241. ];
  1242. continue;
  1243. }
  1244. }
  1245. //
  1246. // Remove tracking URLs and fetch additonal image resources
  1247. //
  1248. $this->resolve_urls($proxy, $out, ["web", "video"]);
  1249. $this->resolve_images($proxy, $out);
  1250. return $out;
  1251. }
  1252. public function image($get){
  1253. // https://image.baidu.com/search/acjson?word=asmr&rn=60&pn=0&newReq=1
  1254. //$json = file_get_contents("scraper/baidu_img.json");
  1255. if($get["npt"]){
  1256. [$params, $proxy] = $this->backend->get($get["npt"], "images");
  1257. $params = json_decode($params, true);
  1258. $params["pn"] = $params["pn"] + 60;
  1259. }else{
  1260. $proxy = $this->backend->get_ip();
  1261. $params = [
  1262. "word" => $get["s"],
  1263. "rn" => 60, // results/page
  1264. "pn" => 0, // item increment (0 * 60)
  1265. "newReq" => 1 // otherwise json is fucked up
  1266. ];
  1267. switch($get["sort"]){
  1268. case "latest": $params["latest"] = 1; break;
  1269. case "hot": $params["hot"] = 1; break;
  1270. }
  1271. if($get["size"] != "any"){
  1272. $params["z"] = $get["size"];
  1273. }
  1274. if($get["ratio"] != "any"){
  1275. $params["imgratio"] = $get["ratio"];
  1276. }
  1277. if($get["format"] != "any"){
  1278. $params["imgformat"] = $get["format"];
  1279. }
  1280. if($get["color"] != "any"){
  1281. $params["ic"] = $get["color"];
  1282. }
  1283. switch($get["type"]){
  1284. case "hd": $params["hd"] = 1; break;
  1285. case "isImgSet": $params["isImgSet"] = 1; break;
  1286. case "copyright": $params["copyright"] = 1; break;
  1287. }
  1288. }
  1289. try{
  1290. $json =
  1291. $this->get(
  1292. $proxy,
  1293. "https://image.baidu.com/search/acjson",
  1294. $params,
  1295. "https://image.baidu.com/search/index?tn=baiduimage&word=" . urlencode($get["s"])
  1296. );
  1297. }catch(Exception $error){
  1298. throw new Exception("Failed to fetch JSON");
  1299. }
  1300. $json = json_decode($json, true);
  1301. if($json === null){
  1302. // detect captcha first
  1303. $this->fuckhtml->load($json);
  1304. $this->detect_ass();
  1305. // fallback to json decode error
  1306. throw new Exception("Failed to decode JSON");
  1307. }
  1308. if(
  1309. isset($json["message"]) &&
  1310. $json["message"] != "success"
  1311. ){
  1312. throw new Exception("Baidu returned an error: {$json["message"]}");
  1313. }
  1314. if(!isset($json["data"]["images"])){
  1315. throw new Exception("Baidu did not return an image object");
  1316. }
  1317. $out = [
  1318. "status" => "ok",
  1319. "npt" => null,
  1320. "image" => []
  1321. ];
  1322. foreach($json["data"]["images"] as $image){
  1323. parse_str(parse_url($image["thumburl"], PHP_URL_QUERY), $thumb_size);
  1324. $out["image"][] = [
  1325. "title" =>
  1326. $this->fuckhtml
  1327. ->getTextContent(
  1328. $image["titleShow"]
  1329. ),
  1330. "source" => [
  1331. [
  1332. "url" => $image["objurl"],
  1333. "width" => (int)$image["width"],
  1334. "height" => (int)$image["height"]
  1335. ],
  1336. [ // thumbnail
  1337. "url" => $image["thumburl"],
  1338. "width" => (int)$thumb_size["w"],
  1339. "height" => (int)$thumb_size["h"]
  1340. ]
  1341. ],
  1342. "url" => $image["fromUrl"]
  1343. ];
  1344. }
  1345. //
  1346. // Detect if there's a next page
  1347. //
  1348. if((int)$json["data"]["totalNum"] >= $params["pn"] + 60){
  1349. $out["npt"] =
  1350. $this->backend->store(
  1351. json_encode($params),
  1352. "images",
  1353. $proxy
  1354. );
  1355. }
  1356. return $out;
  1357. }
  1358. public function video($get){
  1359. // https://www.baidu.com/sf/vsearch?pd=video&tn=vsearch&wd=jak%2Band%2Bdaxter&async=1&pn=0
  1360. // increase &pn +20 for pagination
  1361. //$html = file_get_contents("scraper/baidu_vid.html");
  1362. if($get["npt"]){
  1363. [$params, $proxy] = $this->backend->get($get["npt"], "videos");
  1364. $params = json_decode($params, true);
  1365. $params["pn"] = $params["pn"] + 10;
  1366. }else{
  1367. $proxy = $this->backend->get_ip();
  1368. $params = [
  1369. "pd" => "video",
  1370. "tn" => "vsearch",
  1371. "wd" => $get["s"],
  1372. "async" => 1,
  1373. "pn" => 0
  1374. ];
  1375. }
  1376. try{
  1377. $html =
  1378. $this->get(
  1379. $proxy,
  1380. "https://www.baidu.com/sf/vsearch",
  1381. $params
  1382. );
  1383. }catch(Exception $error){
  1384. throw new Exception("Failed to get search page");
  1385. }
  1386. $html =
  1387. str_replace(
  1388. ["\r", "\n"],
  1389. "",
  1390. $html
  1391. );
  1392. $out = [
  1393. "status" => "ok",
  1394. "npt" => null,
  1395. "video" => [],
  1396. "author" => [],
  1397. "livestream" => [],
  1398. "playlist" => [],
  1399. "reel" => []
  1400. ];
  1401. $html = explode("<script>", $html);
  1402. foreach($html as $result){
  1403. $result = trim($result);
  1404. $this->fuckhtml->load($result);
  1405. // get URL
  1406. preg_match(
  1407. '/<!-- *([^ ]*) *-->/',
  1408. $result,
  1409. $matches
  1410. );
  1411. if(!isset($matches[1])){
  1412. // no link, give up
  1413. continue;
  1414. }
  1415. $link = $matches[1];
  1416. // get title
  1417. $title =
  1418. $this->fuckhtml
  1419. ->getElementsByClassName(
  1420. "video-title",
  1421. "a"
  1422. );
  1423. if(count($title) === 0){
  1424. // should not happen
  1425. continue;
  1426. }
  1427. $title =
  1428. $this->fuckhtml
  1429. ->getTextContent(
  1430. $title[0]
  1431. );
  1432. // get thumbnail
  1433. $img =
  1434. $this->fuckhtml
  1435. ->getElementsByClassName(
  1436. "border-radius",
  1437. "img"
  1438. );
  1439. if(count($img) !== 0){
  1440. $thumb = [
  1441. "url" =>
  1442. $this->unfuckthumb(
  1443. $this->fuckhtml
  1444. ->getTextContent(
  1445. $img[0]["attributes"]["src"]
  1446. )
  1447. ),
  1448. "ratio" => "16:9"
  1449. ];
  1450. }else{
  1451. $thumb = [
  1452. "url" => null,
  1453. "ratio" => null
  1454. ];
  1455. }
  1456. $span =
  1457. $this->fuckhtml
  1458. ->getElementsByTagName(
  1459. "span"
  1460. );
  1461. // get duration
  1462. $duration =
  1463. $this->fuckhtml
  1464. ->getElementsByClassName(
  1465. "video_play_timer",
  1466. $span
  1467. );
  1468. if(count($duration) !== 0){
  1469. $duration =
  1470. $this->hms2int(
  1471. $this->fuckhtml
  1472. ->getTextContent(
  1473. $duration[0]
  1474. )
  1475. );
  1476. }else{
  1477. $duration = null;
  1478. }
  1479. // get author
  1480. // 来源:哔哩哔哩
  1481. $author =
  1482. $this->fuckhtml
  1483. ->getElementsByClassName(
  1484. "wetSource",
  1485. $span
  1486. );
  1487. if(count($author) !== 0){
  1488. $author =
  1489. explode(
  1490. ":",
  1491. $this->fuckhtml
  1492. ->getTextContent(
  1493. $author[0]
  1494. ),
  1495. 2
  1496. )[1];
  1497. }else{
  1498. $author = null;
  1499. }
  1500. // get date posted
  1501. //发布时间:2024-05-06
  1502. // AND get description
  1503. // 简介:Our first look
  1504. $infospans =
  1505. array_merge(
  1506. $this->fuckhtml
  1507. ->getElementsByClassName(
  1508. "c-font-normal",
  1509. $span
  1510. ),
  1511. $this->fuckhtml
  1512. ->getElementsByClassName(
  1513. "c-font-normal",
  1514. "div"
  1515. )
  1516. );
  1517. $date = null;
  1518. $description = null;
  1519. foreach($infospans as $infospan){
  1520. $infospan =
  1521. explode(
  1522. ":",
  1523. $this->fuckhtml
  1524. ->getTextContent(
  1525. $infospan
  1526. ),
  1527. 2
  1528. );
  1529. if(count($infospan) !== 2){
  1530. // should not happen
  1531. continue;
  1532. }
  1533. $infospan[1] =
  1534. $this->fuckhtml
  1535. ->getTextContent(
  1536. $infospan[1]
  1537. );
  1538. switch($infospan[0]){
  1539. case "发布时间": // date posted
  1540. $date = $this->parse_time($infospan[1]);
  1541. break;
  1542. case "简介": // description
  1543. $description = $infospan[1];
  1544. break;
  1545. }
  1546. }
  1547. $out["video"][] = [
  1548. "title" => $this->titledots($title),
  1549. "description" => $this->titledots($description),
  1550. "author" => [
  1551. "name" => $author,
  1552. "url" => null,
  1553. "avatar" => null
  1554. ],
  1555. "date" => $date,
  1556. "duration" => $duration,
  1557. "views" => null,
  1558. "thumb" => $thumb,
  1559. "url" => $link
  1560. ];
  1561. }
  1562. if(count($out["video"]) === 10){
  1563. // assume there's another page after this
  1564. $out["npt"] =
  1565. $this->backend->store(
  1566. json_encode($params),
  1567. "videos",
  1568. $proxy
  1569. );
  1570. }
  1571. return $out;
  1572. }
  1573. public function news($get){
  1574. //$proxy = $this->backend->get_ip();
  1575. //$html = file_get_contents("scraper/baidu.html");
  1576. //$npt_data = [];
  1577. if($get["npt"]){
  1578. [$json, $proxy] = $this->backend->get($get["npt"], "news");
  1579. $json = json_decode($json, true);
  1580. $this->cookie = $json["cookie"];
  1581. $npt_data = $json["req"];
  1582. $npt_data["pn"] = $npt_data["pn"] + 20;
  1583. try{
  1584. $html = $this->get(
  1585. $proxy,
  1586. "https://www.baidu.com/s",
  1587. $npt_data
  1588. );
  1589. }catch(Exception $error){
  1590. throw new Exception("Failed to fetch search page");
  1591. }
  1592. }else{
  1593. //
  1594. // Get authentication token
  1595. //
  1596. $proxy = $this->backend->get_ip();
  1597. $npt_data = [
  1598. "wd" => $get["s"],
  1599. "rn" => 20,
  1600. "tn" => "news"
  1601. ];
  1602. // @TODO add filters
  1603. try{
  1604. $html = $this->get(
  1605. $proxy,
  1606. "https://www.baidu.com/s",
  1607. $npt_data
  1608. );
  1609. }catch(Exception $error){
  1610. throw new Exception("Failed to fetch search page");
  1611. }
  1612. $npt_data["pn"] = 0;
  1613. }
  1614. $data = $this->parse_search($proxy, "news", $npt_data, $html);
  1615. $out = [
  1616. "status" => "ok",
  1617. "npt" => $data["npt"],
  1618. "news" => []
  1619. ];
  1620. foreach($data["web"] as $article){
  1621. $out["news"][] = [
  1622. "title" => $article["title"],
  1623. "author" => null,
  1624. "description" => $article["description"],
  1625. "date" => $article["date"],
  1626. "thumb" => [
  1627. "url" => $article["thumb"]["url"],
  1628. "ratio" => $article["thumb"]["url"] !== null ? "16:9" : null,
  1629. ],
  1630. "url" => $article["url"]
  1631. ];
  1632. }
  1633. return $out;
  1634. }
  1635. private function unfuckthumb($url){
  1636. // probe for proxy URL
  1637. $parsed_url = parse_url($url);
  1638. if(
  1639. preg_match(
  1640. '/^https?:\/\/gimg(?:[0-9]+)?\.baidu\.com/',
  1641. $url
  1642. )
  1643. ){
  1644. $parts = explode("src=", $url);
  1645. if(count($parts) !== 2){
  1646. // shits fucked
  1647. return $url;
  1648. }
  1649. return urldecode(explode("&", $parts[1])[0]);
  1650. }
  1651. $q = explode("&", $url, 2);
  1652. if(count($q) !== 2){
  1653. // shits fucked, again
  1654. return $url;
  1655. }
  1656. // baidu devs are fucking retarded and dont follow spec:
  1657. // &fmt=auto?s=BB32F3A050471AEC72886934030090C4&sec=1753203600&t=0fb2194775d3bd3d1bb114b818479e0a
  1658. parse_str(str_replace("?", "&", $q[1]), $query);
  1659. if(isset($query["size"])){ unset($query["size"]); }
  1660. if(isset($query["q"])){ $query["q"] = "90"; }
  1661. $query = http_build_query($query);
  1662. return
  1663. str_replace(
  1664. $q[1],
  1665. $query,
  1666. $url
  1667. );
  1668. }
  1669. private function titledots($title){
  1670. return trim($title, " .\t\n\r\0\x0B…");
  1671. }
  1672. private function hms2int($time){
  1673. $parts = explode(":", $time, 3);
  1674. $time = 0;
  1675. if(count($parts) === 3){
  1676. // hours
  1677. $time = $time + ((int)$parts[0] * 3600);
  1678. array_shift($parts);
  1679. }
  1680. if(count($parts) === 2){
  1681. // minutes
  1682. $time = $time + ((int)$parts[0] * 60);
  1683. array_shift($parts);
  1684. }
  1685. // seconds
  1686. $time = $time + (int)$parts[0];
  1687. return $time;
  1688. }
  1689. private function parse_viewcount($views){
  1690. if(
  1691. // 10k (wtf lol)
  1692. preg_match(
  1693. '/([0-9]+)万次/',
  1694. $views,
  1695. $matches
  1696. )
  1697. ){
  1698. return (int)$matches[1] * 10000;
  1699. }
  1700. if(
  1701. // units
  1702. preg_match(
  1703. '/([0-9]+)次/',
  1704. $views,
  1705. $matches
  1706. )
  1707. ){
  1708. return (int)$matches[1];
  1709. }
  1710. return null;
  1711. }
  1712. private function parse_time($time){
  1713. // 2023年8月7日 => yyyy/m/d
  1714. if(
  1715. preg_match(
  1716. '/([0-9]{4})年([0-9]{1,2})月([0-9]{1,2})日/',
  1717. $time,
  1718. $matches
  1719. )
  1720. ){
  1721. return strtotime("{$matches[1]}/{$matches[2]}/{$matches[3]}");
  1722. }
  1723. // 昨天11:45 => yesterday at 11:45
  1724. // 昨天 => yesterday
  1725. if(
  1726. preg_match(
  1727. '/昨天(.*)/',
  1728. $time,
  1729. $matches
  1730. )
  1731. ){
  1732. return strtotime("Yesterday {$matches[1]}");
  1733. }
  1734. // 3天前 => 3 days ago
  1735. if(
  1736. preg_match(
  1737. '/([0-9]{1,4})天前/',
  1738. $time,
  1739. $matches
  1740. )
  1741. ){
  1742. return strtotime("{$matches[1]} days ago");
  1743. }
  1744. // 1个月前 => 1 month ago
  1745. if(
  1746. preg_match(
  1747. '/([0-9]{1,4})个月前/',
  1748. $time,
  1749. $matches
  1750. )
  1751. ){
  1752. return strtotime("{$matches[1]} months ago");
  1753. }
  1754. // attempt to parse as-is
  1755. $time = strtotime($time);
  1756. if($time !== false){
  1757. return $time;
  1758. }
  1759. return null;
  1760. }
  1761. private function detect_ass(){
  1762. $as =
  1763. $this->fuckhtml
  1764. ->getElementsByTagName(
  1765. "a"
  1766. );
  1767. if(
  1768. count($as) === 0 ||
  1769. preg_match(
  1770. '/^https?:\/\/wappass\.baidu\.com\/static\/captcha/',
  1771. $this->fuckhtml
  1772. ->getTextContent(
  1773. $as[0]["attributes"]["href"]
  1774. )
  1775. )
  1776. ){
  1777. throw new Exception("Baidu returned a Captcha");
  1778. }
  1779. }
  1780. }