【问题标题】:Scraping a plain text file with no HTML?抓取没有 HTML 的纯文本文件?
【发布时间】:2011-12-08 14:13:30
【问题描述】:

我在纯文本文件中有以下数据:

1.  Value
Location :  Value
Owner:  Value
Architect:  Value

2.  Value
Location :  Value
Owner:  Value
Architect:  Value

... upto 200+ ...

每个段的编号和单词值都会发生变化。

现在我需要将此数据插入 MySQL 数据库。

您对我如何遍历和抓取它有什么建议,以便我可以获取数字旁边的文本值,以及“位置”、“所有者”、“建筑师”的值?

似乎很难使用 DOM 抓取类,因为不存在 HTML 标记。

【问题讨论】:

  • 一个简单的while就足够了。

标签: php screen-scraping


【解决方案1】:

如果数据是不断结构化的,您可以使用fscanf 从文件中扫描它们。

/* Notice the newlines at the end! */
$format = <<<FORMAT
%d. %s
Location :  %s
Owner:  %s
Arcihtect:  %s


FORMAT;

$file = fopen('file.txt', 'r');
while ($data = fscanf($file, $format)) {
    list($number, $title, $location, $owner, $architect) = $data;
    // Insert the data to database here
}
fclose($file);

更多关于fscanf in docs

【讨论】:

  • +1 用于面向行的解析,而且很多人会更喜欢 fscanf 风格,而不是我在自己的评论中提出的正则表达式。我的解决方案对空白和报告行级错误更强大,但它的代码量是两倍。
  • +1 表示fscanf。我不知道这个功能,它看起来真的很有用。
  • 如果值有空格和其他字符,你如何做到这一点?例如"This, is the (value)!".
  • 如果值遵循一个常量结构,只需相应地修改格式字符串。但它可能会变得混乱。如果值变化很大,我建议您使用其他一些解决方案,例如Topener's
【解决方案2】:

如果每个块都具有相同的结构,您可以使用file() 函数:http://nl.php.net/manual/en/function.file.php

$data = file('path/to/file.txt');

这样每一行都是数组中的一个项目,你可以循环遍历它。

for ($i = 0; $i<count($data); $i+=5){
    $valuerow = $data[$i];
    $locationrow = $data[$i+1];
    $ownerrow = $data[$i+2];
    $architectrow = $data[$i+3];
    // strip the data you don't want here, and instert it into the database.
}

【讨论】:

  • 当然里面for和后面的语句应该是sql查询插入数据
  • @Aurelio : 不一定...我总是更喜欢写入制表符分隔符或类似文件,然后使用数据库的批量加载工具(sqlldr、mysql 的 LOAD DATA INFILE 等。它给你一个插入前检查解析的机会。
  • @Joe 解决方案也不错,但 Topener 既没有写我的也没有写你的解决方案。使用他的 for,每次都覆盖相同的变量。这就是我评论的意义。
  • @Aurelio :实际上,它不会覆盖——它是一个奇怪地增长的数组,但什么都不应该被覆盖。虽然,正如 zerodeux 所提到的,不构建数组并在每次循环迭代时转储(最好不要在整个文件中啜饮)会更节省内存
【解决方案3】:

这将与一个非常简单的有状态的面向行的解析器一起工作。您将解析的数据累积到 array() 中的每一行。当有东西告诉你在一个新记录上时,你会转储你解析的内容并再次继续。

面向行的解析器有一个很好的特性:它们需要很少的内存,而最重要的是,常量内存。他们可以毫不费力地处理千兆字节的数据。我正在管理一堆生产服务器,没有什么比那些脚本将整个文件吞入内存更糟糕的了(然后用解析过的内容填充数组,这需要原始文件大小的两倍以上作为内存)。

这很有效,而且几乎是牢不可破的:

<?php
$in_name = 'in.txt';
$in = fopen($in_name, 'r') or die();

function dump_record($r) {
    print_r($r);
}

$current = array();
while ($line = fgets($in)) {
    /* Skip empty lines (any number of whitespaces is 'empty' */
    if (preg_match('/^\s*$/', $line)) continue;

    /* Search for '123. <value> ' stanzas */
    if (preg_match('/^(\d+)\.\s+(.*)\s*$/', $line, $start)) {
        /* If we already parsed a record, this is the time to dump it */
        if (!empty($current)) dump_record($current);

        /* Let's start the new record */
        $current = array( 'id' => $start[1] );
    }
    else if (preg_match('/^(.*):\s+(.*)\s*/', $line, $keyval)) {
        /* Otherwise parse a plain 'key: value' stanza */
        $current[ $keyval[1] ] = $keyval[2];
    }
    else {
        error_log("parsing error: '$line'");
    }
}

/* Don't forget to dump the last parsed record, situation
 * we only detect at EOF (end of file) */
if (!empty($current)) dump_record($current);

fclose($in);
?>

显然,您需要在 function dump_record 中提供适合您口味的内容,例如打印格式正确的 INSERT SQL 语句。

【讨论】:

  • 我编辑了我的评论以强调面向流/行的解析器。 PHP 文化是如此面向 file()/file_get_contents() 的,但这并不能扩展。而且您经常想要扩展,尤其是在像这样的数据导入问题上。一次吃一条记录!
【解决方案4】:

这会给你想要的,

$array = explode("\n\n", $txt);
foreach($array as $key=>$value) {
    $id_pattern = '#'.($key+1).'. (.*?)\n#';
    preg_match($id_pattern, $value, $id);

    $location_pattern = '#Location \: (.*?)\n#';
    preg_match($location_pattern, $value, $location);


    $owner_pattern = '#Owner\: (.*?)\n#';
    preg_match($owner_pattern, $value, $owner);


    $architect_pattern = '#Architect\: (.*?)#';
    preg_match($architect_pattern, $value, $architect);

    $id = $id[1];
    $location = $location[1];
    $owner = $owner[1];
    $architect = $architect[1];

    mysql_query("INSERT INTO table (id, location, owner, architect) VALUES ('".$id."', '".$location."', '".$owner."', '".$architect."')");
//Change MYSQL query

}

【讨论】:

    【解决方案5】:

    同意Topener方案,这里是一个例子,如果每个块是4行+空行:

    $data = file('path/to/file.txt');
    $id = 0;
    $parsedData = array();
    foreach ($data as $n => $row) {
      if (($n % 5) == 0) $id = (int) $row[0];
      else {
        $parsedData[$id][$row[0]] = $row[1];
      }
    }
    

    结构将很方便使用,对于 MySQL 或其他什么。我没有添加代码来删除第一段中的冒号。

    祝你好运!

    【讨论】:

      【解决方案6】:
      preg_match_all("/(\d+)\.(.*?)\sLocation\s*\:\s*(.*?)\sOwner\s*\:\s*(.*?)\sArchitect\s*\:\s*(.*?)\s?/i",$txt,$m);
      
      $matched = array();
      
      foreach($m[1] as $k => $v) {
      
          $matched[$v] = array(
              "location" => trim($m[2][$v]),
              "owner" => trim($m[3][$v]),
              "architect" => trim($m[4][$v])
          );
      
      }
      

      【讨论】:

        猜你喜欢
        • 2013-06-10
        • 1970-01-01
        • 1970-01-01
        • 2018-12-26
        • 1970-01-01
        • 1970-01-01
        • 2011-10-08
        • 2012-04-03
        • 1970-01-01
        相关资源
        最近更新 更多