如何使用PhantomJS爬取链接

14

能否使用PhantomJS替代BeautifulSoup

我正在尝试在Etsy上搜索并访问所有相关链接。在Python中,我知道如何做到这一点(使用BeautifulSoup),但今天我想看看是否可以使用PhantomJS做同样的事情。但是我卡住了。

这个脚本应该在Etsy上搜索“hello kitty”,并返回所有产品的<a class="listing-thumb" href=...></a>,并将它们打印在控制台上。理想情况下,我会稍后访问它们并获取所需信息。但现在它只是卡住了。有任何想法吗?

var page = require('webpage').create();
var url = 'http://www.etsy.com/search?q=hello%20kitty';

page.open(url, function(status){
    // list all the a.href links in the hello kitty etsy page
    var link = page.evaluate(function() {
        return document.querySelectorAll('a.listing-thumb');
    });
    for(var i = 0; i < link.length; i++){ console.log(link[i].href); }
    phantom.exit();
});

我已经考虑过使用CasperJS,它可能更适合这个任务的设计。

1
我建议查看 cheerio。它非常适合用于网页抓取,并且其遍历/操作API与jQuery非常相似。 - davidchambers
3个回答

35

PhantomJS evaluate() 无法序列化和返回复杂对象,比如 HTMLElements 或 NodeLists,因此在返回之前,您需要将它们映射为可序列化的东西:

var page = require('webpage').create();
var url = 'http://www.etsy.com/search?q=hello%20kitty';

page.open(url, function(status) {
    // list all the a.href links in the hello kitty etsy page
    var links = page.evaluate(function() {
        return [].map.call(document.querySelectorAll('a.listing-thumb'), function(link) {
            return link.getAttribute('href');
        });
    });
    console.log(links.join('\n'));
    phantom.exit();
});

注意:这里我们使用[].map.call(),以便将NodeList视为标准的Array


7
+1 希望文档中明确说明这一点。作为新手,我花了几个小时才找到这个答案! :-( - Alberto
2
您是指CasperJS文档图表中的“返回本地类型”箭头,还是PhantomJS文档中的注释“*evaluate函数的参数和返回值必须是简单的原始对象[...]”(注意:CasperJS中到WebPage.evaluate的链接是错误的)?我肯定我漏掉了什么,因为图表中的脚本很容易被忽视,作为新手,我天真地认为我可以不用PhantomJS文档。无论如何,感谢这个fantastic工具。 - Alberto

4
您的代码唯一的问题是您不理解phantomjs的作用域。它有phantom和page两个作用域。您试图从页面作用域(page.evaluate在页面作用域中运行)返回JavaScript DOM对象引用(这些不能被序列化)到phantom主作用域,我认为这是不可能的。以下是可行的代码:
var page = require('webpage').create();
var url = 'http://www.etsy.com/search?q=hello%20kitty';

// for debug (to see if page returns status code 200)
page.onResourceReceived = function(response) {
    if (response.url === url) {
        console.log('Resorce: "' + response.url + '" status: '  + response.status);

        if (response.status === 200) {
            console.log(response.url);
            for (var i = 0; i < response.headers.length; i++) {
                console.log(response.headers[i].name + ': ' + response.headers[i].value);
            }
        }
    }
};

page.onLoadFinished = function(status){
    console.log('Status: ' + status);

    console.log('Starting evaluate...');
    var links = page.evaluate(function() {
        var nodes = [],
            matches = document.querySelectorAll("a.listing-thumb");

            for(var i = 0; i < matches.length; ++i) {
                nodes.push(matches[i].href);
            }

            return nodes;
    });
    console.log('Done evaluate... count: ' + links.length);

    if (links && links.length > 0) {
        for(var i = 0; i < links.length; ++i) {
            console.log('(' + i + ') ' + links[i]);
        }
    } else {
        console.log("No match found!");
    }

    phantom.exit(0);
};

page.open(url);

2

这是我最近编写的一些使用PhantomJs爬取URL的代码,如果您只提供一个URL,它将显示页面上的所有URL,如果您提供一个class|id参数,后面跟着一个"class/id名称",它将仅显示类/ id的URL。

////////////////////////////////////////////////////////// 
/////  PhantomJS URL Scraper v.1.3 ///// 
// 
// Copyrighted by +A.M.Danischewski  2016+ (c)
// This program may be reutilized without limits, provided this 
// notice remain intact. 
// 
// Usage: phantomjs phantom_urls.js <URL> [["class"|"id"] [<query id/class name>]]
//
//   Argument 1: URL -- "https://www.youtube.com/watch?v=8TniRMwL2Vg" 
//   Argument 2: "class" or "id" 
//   Argument 3: If Argument 2 was provided, "class name" or "id name" 
// 
// By default this program will display ALL urls from a user supplied URL.  
// If a class name or id name is provided then only URL's from the class 
// or id are displayed.  
//  
/////////////////////////////////// 

var page = require('webpage').create(), 
    system = require('system'),
    address;

if (system.args.length === 1) {
  console.log(' Usage: phantomjs phantom_urls.js <URL> [["class"|"id"] [<query id/class name>]]');
  phantom.exit();
}

address = system.args[1];
querytype= system.args[2];
queryclass = system.args[3];
page.open(address, function(status) {
  if (status !== 'success') {
    console.log('Error loading address: '+address);
  } else {
   //console.log('Success! In loading address: '+address);   
  }
});

page.onConsoleMessage = function(msg) {
  console.log(msg);
}

page.onLoadFinished = function(status) {
   var dynclass="function() { window.class_urls = new Array(); window.class_urls_next=0; var listings = document.getElementsByClassName('"+queryclass+"'); for (var i=0; i < listings.length; i++) { var el = listings[i]; var ellnks=[].map.call(el.querySelectorAll('a'),function(link) {return link.getAttribute('href');}); var elhtml=el.innerHTML; window.class_urls.push(ellnks.join('\\n')); }; return window.class_urls;}"; 
   var    dynid="function() { window.id_urls = new Array(); window.id_urls_next=0; var listings = document.getElementById('"+queryclass+"'); var ellnks=[].map.call(listings.querySelectorAll('a'),function(link) {return link.getAttribute('href');}); var elhtml=listings.innerHTML; window.id_urls.push(ellnks.join('\\n'));  return window.id_urls;}";  
   var  allurls="function() { var links = page.evaluate(function() { return [].map.call(document.querySelectorAll('a'), function(link) { return link.getAttribute('href'); };); };); console.log(links.join('\\n')); }"; 
   var page_eval_function="";  
   if (querytype === "class") {
   console.log(page.evaluate(dynclass).toString().replace(/,/g, "\n")); 
   } else if (querytype === "id") {
   console.log(page.evaluate(dynid).toString().replace(/,/g, "\n")); 
   } else { 
   var links = page.evaluate(function() {
        return [].map.call(document.querySelectorAll('a'), function(link) {
            return link.getAttribute('href');
        });
    });    
       console.log(links.join('\n'));
   }             
   phantom.exit();
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接