写网络爬虫用哪些编程语言比较好?

3

我有丰富的PHP经验,但我意识到对于大规模的网络爬虫来说,PHP可能不是最好的语言,因为一个进程无法无限期地运行。人们建议使用哪些语言?

7个回答

10

大多数编程语言都可以胜任,关键组件如下:

  1. 用于处理网络协议的库
  2. 用于处理正则表达式的库
  3. 用于解析HTML内容的库

现在大多数编程语言都有良好支持上述功能的库。当然,您还需要一些方法来保存结果,例如数据库。

比语言更重要的是理解需要处理的所有概念。以下是一些Python示例,可以帮助您入门。

http://www.example-code.com/python/pythonspider.asp


6
任何一种语言都可以,只要它有一个好的网络库并支持解析您想要爬取的格式。这些确实是唯一的资格要求。

这些是使其成为“可能”的资格,但我认为使其成为一个适合此类操作的语言的资格要求更严格(尤其是速度方面)。 - ssube
2
@peachykeen:没有后两者也是可能的,只是需要更多的工作。至于速度,我想INTERCAL可能不是爬虫的最佳选择,但我不明白为什么速度对于网络爬虫比其他任何类型的程序更重要(特别是考虑到Web-anything极有可能受到IO限制)。你的爬虫必须非常慢才能使其执行时间超过Web的延迟。 - Chuck
可能是这样,但我仍然会考虑一下。我不会在单独的服务器上运行爬虫程序,所以它需要与我的台式机或服务器共享时间。此外,我见过一些恶心的网页,在大多数HTML解析器中加载需要可测量的时间,所以你必须小心处理。网络可能是你的瓶颈,但我认为速度值得简要提及。 :) - ssube
1
而且同样重要的是,另一个资格是“一种语言,让你可以轻松地做除了爬行之外的所有事情”。这使得你只是收集、处理、重度处理或者无论你用数据做什么都有所不同。 - Chubas

1
你可以考虑使用Python和PyGtkMozEmbed或PyWebKitGtk加上JavaScript来创建你的爬虫。
在页面和所有其他脚本加载完成后,可以使用JavaScript进行爬取。
这样你就会拥有少数支持JavaScript的网络爬虫之一,并且可能会发现其他爬虫无法看到的一些隐藏内容 :)

0

在编写多核/线程爬虫方面,C语言是万能的神器,但它也有自己的复杂性。在C语言之后,一些人选择JAVA(因为其广泛的探索和使用),而其他人则转向Python。如果您有良好的架构,我可以保证这三种语言不会限制您的效率。

这段Python代码是一个Curl实现,可以在一个不错的服务器上在300秒内爬取大约10,000个页面。

#! /usr/bin/env python
# -*- coding: iso-8859-1 -*-
# vi:ts=4:et
# $Id: retriever-multi.py,v 1.29 2005/07/28 11:04:13 mfx Exp $

#
# Usage: python retriever-multi.py <file with URLs to fetch> [<# of
#          concurrent connections>]
#

import sys
import pycurl

# We should ignore SIGPIPE when using pycurl.NOSIGNAL - see
# the libcurl tutorial for more info.
try:
    import signal
    from signal import SIGPIPE, SIG_IGN
    signal.signal(signal.SIGPIPE, signal.SIG_IGN)
except ImportError:
    pass


# Get args
num_conn = 10
try:
    if sys.argv[1] == "-":
        urls = sys.stdin.readlines()
    else:
        urls = open(sys.argv[1]).readlines()
    if len(sys.argv) >= 3:
        num_conn = int(sys.argv[2])
except:
    print "Usage: %s <file with URLs to fetch> [<# of concurrent connections>]" % sys.argv[0]
    raise SystemExit


# Make a queue with (url, filename) tuples
queue = []
for url in urls:
    url = url.strip()
    if not url or url[0] == "#":
        continue
    filename = "doc_%03d.dat" % (len(queue) + 1)
    queue.append((url, filename))


# Check args
assert queue, "no URLs given"
num_urls = len(queue)
num_conn = min(num_conn, num_urls)
assert 1 <= num_conn <= 10000, "invalid number of concurrent connections"
print "PycURL %s (compiled against 0x%x)" % (pycurl.version, pycurl.COMPILE_LIBCURL_VERSION_NUM)
print "----- Getting", num_urls, "URLs using", num_conn, "connections -----"


# Pre-allocate a list of curl objects
m = pycurl.CurlMulti()
m.handles = []
for i in range(num_conn):
    c = pycurl.Curl()
    c.fp = None
    c.setopt(pycurl.FOLLOWLOCATION, 1)
    c.setopt(pycurl.MAXREDIRS, 5)
    c.setopt(pycurl.CONNECTTIMEOUT, 30)
    c.setopt(pycurl.TIMEOUT, 300)
    c.setopt(pycurl.NOSIGNAL, 1)
    m.handles.append(c)


# Main loop
freelist = m.handles[:]
num_processed = 0
while num_processed < num_urls:
    # If there is an url to process and a free curl object, add to multi stack
    while queue and freelist:
        url, filename = queue.pop(0)
        c = freelist.pop()
        c.fp = open(filename, "wb")
        c.setopt(pycurl.URL, url)
        c.setopt(pycurl.WRITEDATA, c.fp)
        m.add_handle(c)
        # store some info
        c.filename = filename
        c.url = url
    # Run the internal curl state machine for the multi stack
    while 1:
        ret, num_handles = m.perform()
        if ret != pycurl.E_CALL_MULTI_PERFORM:
            break
    # Check for curl objects which have terminated, and add them to the freelist
    while 1:
        num_q, ok_list, err_list = m.info_read()
        for c in ok_list:
            c.fp.close()
            c.fp = None
            m.remove_handle(c)
            print "Success:", c.filename, c.url, c.getinfo(pycurl.EFFECTIVE_URL)
            freelist.append(c)
        for c, errno, errmsg in err_list:
            c.fp.close()
            c.fp = None
            m.remove_handle(c)
            print "Failed: ", c.filename, c.url, errno, errmsg
            freelist.append(c)
        num_processed = num_processed + len(ok_list) + len(err_list)
        if num_q == 0:
            break
    # Currently no more I/O is pending, could do something in the meantime
    # (display a progress bar, etc.).
    # We just call select() to sleep until some more data is available.
    m.select(1.0)


# Cleanup
for c in m.handles:
    if c.fp is not None:
        c.fp.close()
        c.fp = None
    c.close()
m.close()

0

C++ - 如果你知道你在做什么。你将不需要一个 Web 服务器和 Web 应用程序,因为爬虫毕竟只是一个客户端。


1
真的!我想我只需要与某种数据库接口... 你对Python有什么想法吗? - Shamoon
2
PHP不需要Web服务器来使用其CLI版本。这只是一个“额外信息”的小贴士。 - Jim
嗯... PHP 可以在 CLI 中运行,但它仍然很快会溢出内存,不是吗? - Shamoon
没有头绪,从未尝试在PHP中创建这样的应用程序。不过我认为应该是可以的。 - Jim
1
我认为PHP非常适合这个任务。你可以增加进程允许消耗的内存量,运行时间等等。你可以让线程在一段时间后自动结束,只需跟踪数据库中仍需要索引的内容,然后让新线程从旧线程离开的地方继续工作。 - Dagg Nabbit
除非你在PHP中遇到了一个导致内存泄漏的错误,否则你不应该在长时间运行时遇到任何问题。你只需要将使用的内存保持最小化,这也是你在任何其他语言中都需要做的事情。 - deceze

0

-3

C#和C++可能是最适合的两种语言,只是取决于你更熟悉哪一种以及哪一种更快(C#可能更容易些)。

我不建议使用Python、Javascript或PHP。它们在文本处理方面通常比C系列语言慢。如果你想爬取任何大量的网页,你需要尽可能地提高速度。

我以前用过C#和HtmlAgilityPack来做这件事,它的效果相当不错,而且很容易上手。能够使用许多与XML相同的命令来处理HTML使其非常好用(我有在C#中处理XML的经验)。

你可能需要测试可用的C# HTML解析库与C++解析库的速度。我知道在我的应用程序中,我每秒运行60-70个相当混乱的页面,并从每个页面中提取了大量的数据(但那是一个布局相当固定的网站)。

编辑:我注意到你提到了访问数据库。C++和C#都有用于大多数常见数据库系统的库,从SQLite(对于在几个站点上进行快速爬虫非常好)到中端引擎如MySQL和MSSQL,再到更大的DB引擎(我从未使用过Oracle或DB2,但这是可能的)。


5
[citation needed] 对于JavaScript和Python在这项任务中过于缓慢的说法需要证据支持。Googlebot是用Python编写的,那时候Python和电脑硬件都比现在要慢得多。 - Chuck
@aaronasterling:但是用PHP编写会比C++运行速度慢。根据需要处理的处理复杂度和需要保存到磁盘的数据(比如你需要遍历DOM并将每个img src保存到数据库中),C++/C#可以大大提高性能。实际解析都是文本操作,而且数量很多。总体而言,无论网络速度如何,C++或C#都提供更好的处理性能。 - ssube
@aaronasterling:但是网络爬虫独占CPU时间的机会有多大?我曾经见过一些用C#编写的页面加载需要半秒钟,因为它们太混乱了。虽然在这种情况下可能不是关键问题,但解析速度仍然是我考虑的因素之一。 - ssube
1
实际上,有历史性的例子表明,用“慢”语言编写的代码最终能够击败用C语言编写的代码。以Apache(不是用C编写的Apache2)和Tclhttpd(用Tcl编写,当时甚至比Perl还要慢)为例。在静态文件传输方面,Tclhttpd轻松击败了Apache。Apache2从中吸取教训,改变了其I/O算法,使其更像tcl解释器的做法。因此,C/C++可能并不是最快的,仅仅因为它在加法运算方面很快。 - slebetman
2
@peachykeen:另一方面,许多解释器(尤其是针对像Perl和Tcl这样的旧语言)都有优化的字符串操作例程。而C/C++则具有非常缓慢的字符串操作例程。比较一下C中的strlen()(时间复杂度为线性)和Tcl中的string length(时间复杂度为常数)。这是因为高级语言中的“字符串”通常在后端作为适当的数据结构实现,而不是裸的C字符串。当然,并没有保证。例如,Microsoft的JavaScript表现得好像String对象只是C字符串的薄包装器。 - slebetman
显示剩余7条评论

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