38

我正在并行运行许多网络爬虫实例。

每个爬虫从表中选择一个域,将该 URL 和开始时间插入到日志表中,然后开始爬取该域。

其他并行爬虫在选择自己的要爬取的域之前检查日志表以查看哪些域已经被爬取。

我需要防止其他爬虫选择一个刚刚被另一个爬虫选择但还没有日志条目的域。我对如何做到这一点的最佳猜测是在一个爬虫选择一个域并在日志表中插入一行(两个查询)时锁定数据库以防止所有其他读/写操作。

到底是怎么做到的?恐怕这非常复杂,并且依赖于许多其他事情。请帮助我开始。


这段代码似乎是一个很好的解决方案(但是请参阅下面的错误):

INSERT INTO crawlLog (companyId, timeStartCrawling)
VALUES
(
    (
        SELECT companies.id FROM companies
        LEFT OUTER JOIN crawlLog
        ON companies.id = crawlLog.companyId
        WHERE crawlLog.companyId IS NULL
        LIMIT 1
    ),
    now()
)

但我不断收到以下mysql错误:

You can't specify target table 'crawlLog' for update in FROM clause

有没有办法在没有这个问题的情况下完成同样的事情?我尝试了几种不同的方法。包括这个:

INSERT INTO crawlLog (companyId, timeStartCrawling)
VALUES
(
    (
        SELECT id
        FROM companies
        WHERE id NOT IN (SELECT companyId FROM crawlLog) LIMIT 1
    ),
    now()
)
4

5 回答 5

55

您可以使用 MySQLLOCK TABLES命令锁定表,如下所示:

LOCK TABLES tablename WRITE;

# Do other queries here

UNLOCK TABLES;

看:

http://dev.mysql.com/doc/refman/5.5/en/lock-tables.html

于 2011-07-08T07:34:10.527 回答
5

好吧,表锁是解决这个问题的一种方法;但这使得并行请求成为不可能。如果表是 InnoDB,则可以强制行锁定,在事务中使用SELECT ... FOR UPDATE 。

BEGIN;

SELECT ... FROM your_table WHERE domainname = ... FOR UPDATE

# do whatever you have to do

COMMIT;

请注意,您将需要一个索引domainname(或您在 WHERE 子句中使用的任何列)才能使其工作,但这通常是有道理的,我假设您无论如何都会拥有它。

于 2011-07-08T07:55:11.027 回答
4

您可能不想锁定表。如果您这样做,您将不得不担心当其他爬虫尝试写入数据库时​​捕获错误 - 这就是您在说“......非常复杂并且依赖于许多其他事情”时所想的。

相反,您可能应该将查询组包装在 MySQL 事务中(请参阅http://dev.mysql.com/doc/refman/5.0/en/commit.html),如下所示:

START TRANSACTION;
SELECT @URL:=url FROM tablewiththeurls WHERE uncrawled=1 ORDER BY somecriterion LIMIT 1;
INSERT INTO loggingtable SET url=@URL;
COMMIT;

或者类似的东西。

[编辑] 我刚刚意识到 - 您可能可以在一个查询中完成您需要的所有事情,甚至不必担心交易。像这样的东西:

INSERT INTO loggingtable (url) SELECT url FROM tablewithurls u LEFT JOIN loggingtable l ON l.url=t.url WHERE {some criterion used to pick the url to work on} AND l.url IS NULL.
于 2011-07-08T07:55:03.177 回答
2

我不会使用锁定或事务。

最简单的方法是在记录表中插入一条记录(如果它尚不存在),然后检查该记录。

假设你有tblcrawels (cra_id)一个充满了你的爬虫并且tblurl (url_id)充满了 URL 和一个tbllogging (log_cra_id, log_url_id)用于你的日志文件的表。

如果爬虫 1 想要开始爬取 url 2,您将运行以下查询:

INSERT INTO tbllogging (log_cra_id, log_url_id) 
SELECT 1, url_id FROM tblurl LEFT JOIN tbllogging on url_id=log_url 
WHERE url_id=2 AND log_url_id IS NULL;

下一步是检查是否已插入此记录。

SELECT * FROM tbllogging WHERE log_url_id=2 AND log_cra_id=1

如果您得到任何结果,则爬虫 1 可以爬取此网址。如果您没有得到任何结果,这意味着另一个爬虫已插入同一行并且已经在爬取。

于 2011-07-08T08:06:32.540 回答
2

我从@Eljakim 的回答中获得了一些灵感,并开始了这个新线程,在那里我发现了一个很棒的技巧。它不涉及锁定任何东西并且非常简单。

INSERT INTO crawlLog (companyId, timeStartCrawling)
SELECT id, now()
FROM companies
WHERE id NOT IN
(
    SELECT companyId
    FROM crawlLog AS crawlLogAlias
)
LIMIT 1
于 2011-07-08T21:18:22.440 回答