从网站抓取数据并将其用于 LaTeX 文档

从网站抓取数据并将其用于 LaTeX 文档

这是对“列表中的换行和对齐“。

\documentclass[
  a4paper,
  12pt
]{article}

\usepackage[
  hmargin=2.4cm,
  vmargin=3cm
]{geometry}
\usepackage{amssymb}
\usepackage{enumitem}
\usepackage{totcount}
\usepackage[
  colorlinks=true,
  urlcolor=black
]{hyperref}

\newcommand*\film[4]{%
 \item[\refstepcounter{enumi}\textcolor{blue}{$\maltese$} \textsf{[#4]}] \textsf{\href{#1}{#2}~(#3)}}  

\regtotcounter{enumi}

\begin{document}

\begin{enumerate}[leftmargin=5em,labelindent=-5em]
  \film{http://www.imdb.com/title/tt0134273/}{8MM}{1999}{6,4}
  \film{http://www.imdb.com/title/tt0050083/}{12 Angry Men}{1957}{8,9}
  \film{http://www.imdb.com/title/tt0057181/}{Incredibly Strange Creatures Who Stopped Living and Became Mixed-Up Zombies, The}{2006}{2,1}
\end{enumerate}

I have \total{enumi}~films.

\end{document}

现在 David 已经帮助我按顺序排列垂直对齐,我想让文档更加自动化:可以从互联网数据库站点并将其作为命令的最后一个参数插入到文档中\film?我能否以某种方式自动从http://www.imdb.com/title/tt0134273/并将其插入到文档中?

关键是评级会随着时间而改变,这样我就不必为每部电影手动更新条目。

答案1

要下载此系统的最新版本,请运行

git clone https://github.com/vermiculus/imdb-tex.git && make install && make demo

看到 LaTeX 在这方面的多功能性真是太有趣了;我可以想象这个脚本的一个版本也包含电影海报。下面是一个完整的可编译示例;它将创建必要的文件。排版文档一次(以创建这些文件)后,运行它make all即可看到它飞起来。我将在后面的段落中解释我所做的工作。

我要指出的是,在这种情况下以及类似的情况下,此类问题与 StackOverflow 交叉——您需要做的就是将格式化的数据插入文件中。

TeX 系统最大的优势之一是它能够自动生成并处理成可读文档。(我想这就是列车时刻表已排版。)实际上,可能性是惊人的;您可以(使用 IMDB 的 API,如果存在的话)轻松创建一个系统,该系统将生成数据库中每部电影的报告。我不建议您这样做;结果将是巨大的;-)。

我没有在其中使用任何 API 或花哨的东西;以下只是一个概念验证。这个想法源于这样一个事实:与 LaTeX 文档一样,(X)HTML 文档天生就是结构化的。使用 Python(或您最喜欢的语言),我们可以利用这种结构在网站中搜索关键数据。我们将创建一种方法来搜索文本中的关键数据(迄今为止系统中最难的部分),然后从我们从电影标识符生成的 URL 创建和下载源代码。然后,我们只需打开 TeX 文件,找到我们想要插入数据的位置,使用我们制作的“关键数据”函数将其插入,然后关闭文件。我们将能够使用一个简单的脚本

./imdb.py <template file> <movie ids...>

让我们创建一个名为的文件imdb.py并使其可执行:

touch imdb.py
chmod +x imdb.py

并在第一行告诉它如何运行:

#!/usr/bin/env python

(请注意,这只适用于 UNIX 系统;如果您没有像 MinGW 或 Cygwin 这样的 UNIX 仿真解决方案,请对 Windows 进行必要的修改。)

我们定义一个函数来抓取特定分隔符内的文本。(你可以想象这样的场景<filmrating>6.4</filmrating>,尽管在这种情况下它不是那么有表现力。愚蠢的 IMDB。)

def get_middle(s, start, end):
    import re
    return re.search(re.escape(start) + '(.*?)' + re.escape(end), s).group(1)

此函数使用正则表达式 ( re) 来搜索某些<start>...<end>模式。我们将 分组...在括号中并仅返回该文本。您可以通过快速 Google 搜索了解有关正则表达式的更多信息,尽管我建议python在该查询中添加内容;RE 本质上是一个 CS 理论主题。

接下来,我们要创建一个函数,该函数将根据我们指定的电影 ID 创建 URL。(请注意,电影 ID 是 中title/...http://www.imdb.com/title/tt0134273;因此tt0134273。)这比上一个函数更简单:

def make_url(movie_id):
    return 'http://www.imdb.com/title/{mid}'.format(
        mid=movie_id)

由于这是一个支持互联网的解决方案,我们应该创建一个函数来获取存储在某个 URL(可能是我们刚刚生成的 URL)上的 HTML。我们将创建一个函数来执行此操作:

def get_source(movie_id):
    import urllib2
    url = make_url(movie_id)
    stream = urllib2.urlopen(url)
    return stream.read()

很简单,对吧?现在我们只想定义几个“易读”函数;这些函数的存在只是为了使代码更具可读性。这些函数获得不同的元数据,我可能会在未来的版本中将它们整合到一个具有更好界面的函数中。

def get_rating(source):
    return get_middle(source, '<span itemprop="ratingValue">', '</span>')

def get_title(source):
    return get_middle(source, '<span class="itemprop" itemprop="name">','</span>')

def get_release(source):
    return get_middle(source, '<meta itemprop="datePublished" content="','" />')

现在我们只需创建一个函数来处理繁重的工作并协调我们迄今为止编写的所有内容。我们创建一个函数,该函数接受电影 ID、模板文件和可选标记。

  1. 我们首先要打开文件,将文件读入行列表(file_lines)。
  2. 然后我们在文件中找到我们的标记。
  3. 我们从 IMDB 下载电影页面并将其存储在名为 的变量中source
  4. 我们创建一个变量,data来保存我们想要插入的行,并使用我们之前创建的“可读性函数”用我们所有的元数据对其进行格式化。
  5. 我们在data标记之前插入(因此标记仍然存在下面我们刚刚插入的行)。
  6. 完成了!写入文件。
def insert(movie_id, file_name, marker='%! FILMDATA !%\n'):
    """Insert information for this `movie-id` before the %! FILMDATA !% line in `file_name`.

This function should not delete the marker line.
"""
    with open(file_name, 'r') as f:
        file_lines = list(f)

    marker_line = file_lines.index(marker)

    source = get_source(movie_id)
    data = '\\film{%s}{%s}{%s}{%s}\n' % (
        make_url(movie_id),
        get_title(source),
        get_release(source),
        get_rating(source))

    file_lines.insert(marker_line, data)

    with open(file_name, 'w') as f:
        f.writelines(file_lines)

最后一点处理它在命令行上的使用。我们将它file_name作为第一个命令行参数(技术上是第二个,实际的第一个参数是正在运行的程序的名称)。然后我们循环遍历其余参数,将它们中的每一个插入到提供的文件中。

def main():
    import sys
    file_name = sys.argv[1]
    for movie_id in sys.argv[2:]:
        insert(movie_id, file_name)

if __name__ == '__main__':
    import sys
    print 'Welcome.'
    print 'Inserting data for {}'.format(str(sys.argv[2:]))
    main()
    print 'Done.'

本练习的目的是展示看似非常复杂的任务可以通过组合工具轻松处理。由于 TeX 是一种基于文本的格式,因此非常易于在无数编程语言中生成和操作。我希望这为 TeX 系统的持续发展提供了一个令人信服的论据。:)

输出

该工具的任何进一步开发都将在在 Github 上


完整、可编译的代码:

\begin{filecontents*}{makefile}
IDS=tt0134273 tt0050083 tt0057181
TEMPLATE=imdb-template.tex


grab:
    ./imdb.py $(TEMPLATE) $(IDS)

tex:
    pdflatex $(TEMPLATE)
        pdflatex $(TEMPLATE)

all: grab tex
\end{filecontents*}
\begin{filecontents*}{imdb.py}
#!/usr/bin/env python

def get_middle(s, start, end):
    import re
    return re.search(re.escape(start) + '(.*?)' + re.escape(end), s).group(1)

def make_url(movie_id):
    return 'http://www.imdb.com/title/{mid}'.format(
        mid=movie_id)

def get_source(movie_id):
    import urllib2
    url = make_url(movie_id)
    stream = urllib2.urlopen(url)
    return stream.read()

def get_rating(source):
    # YYYY-MM-DD
    iso = get_middle(source, '<span itemprop="ratingValue">', '</span>')
    return iso.split('-')[0] # YYYY

def get_title(source):
    return get_middle(source, '<span class="itemprop" itemprop="name">','</span>')

def get_release(source):
    return get_middle(source, '<meta itemprop="datePublished" content="','" />')

def insert(movie_id, file_name, marker='%! FILMDATA !%\n'):
    """Insert information for this `movie-id` before the %! FILMDATA !% line in `file_name`.

This function should not delete the marker line.
"""
    with open(file_name, 'r') as f:
        file_lines = list(f)

    marker_line = file_lines.index(marker)

    source = get_source(movie_id)
    data = '\\film{%s}{%s}{%s}{%s}\n' % (
        make_url(movie_id),
        get_title(source),
        get_release(source),
        get_rating(source))

    file_lines.insert(marker_line, data)

    with open(file_name, 'w') as f:
        f.writelines(file_lines)

def main():
    import sys
    file_name = sys.argv[1]
    for movie_id in sys.argv[2:]:
        insert(movie_id, file_name)

if __name__ == '__main__':
    import sys
    print 'Welcome.'
    print 'Inserting data for {}'.format(str(sys.argv[2:]))
    main()
    print 'Done.'
\end{filecontents*}
\documentclass[
  a4paper,
  12pt
]{article}

\usepackage[
  hmargin=2.4cm,
  vmargin=3cm
]{geometry}
\usepackage{amssymb}
\usepackage{enumitem}
\usepackage{totcount}
\usepackage[
  colorlinks=true,
  urlcolor=black
]{hyperref}

\newcommand*\film[4]{%
 \item[\refstepcounter{enumi}\textcolor{blue}{$\maltese$} \textsf{[#4]}] \textsf{\href{#1}{#2}~(#3)}}  

\regtotcounter{enumi}

\begin{document}
\begin{enumerate}[leftmargin=5em,labelindent=-5em]
%! FILMDATA !%
\end{enumerate}

I have \total{enumi}~films.
\end{document}

答案2

LuaTeX 非常适合此类任务。LuaTeX 有一个套接字库,可用于从 URL 获取数据。在 ConTeXt 中,几乎所有与文件相关的命令(\readfile\typefile\externalimage等)也可以从 URL 获取数据。

但是,要获取 IMDB 评分,使用 API 比解析 HTML 页面中的数据更简单。IMDB 数据的一个非官方 API 是http://www.omdbapi.com/

以下实现使用电影标题来搜索 IMDB。返回值是一个 JSON 字符串。例如,当您搜索十二怒汉,返回的值是(我重新格式化了它,以便于阅读)

{
   "Title":"12 Angry Men",
   "Year":"1957",
   "Rated":"NOT RATED",
   "Released":"1 Apr 1957",
   "Runtime":"96 min",
   "Genre":"Drama",
   "Director":"Sidney Lumet",
   "Writer":"Reginald Rose (story)",
   "Actors":"Martin Balsam, John Fiedler, Lee J. Cobb, E.G. Marshall",
   "Plot":"A dissenting juror in a murder trial slowly manages to convince the others that the case is not as obviously clear as it seemed in court.",
   "Language":"English",
   "Country":"USA",
   "Awards":"Nominated for 3 Oscars. Another 18 wins & 6 nominations.",
   "Poster":"http://ia.media-imdb.com/images/M/MV5BODQwOTc5MDM2N15BMl5BanBnXkFtZTcwODQxNTEzNA@@._V1_SX300.jpg",
   "Metascore":"N/A",
   "imdbRating":"8.9",
   "imdbVotes":"367967",
   "imdbID":"tt0050083",
   "Type":"movie",
   "Response":"True"
}

然后我使用该json模块将其转换为 Lua 表,然后可以轻松查询相关信息。例如,与上述 json 字符串对应的 Lua 表是(注意键已按字母顺序排序):

{
 ["Actors"]="Martin Balsam, John Fiedler, Lee J. Cobb, E.G. Marshall",
 ["Awards"]="Nominated for 3 Oscars. Another 18 wins & 6 nominations.",
 ["Country"]="USA",
 ["Director"]="Sidney Lumet",
 ["Genre"]="Drama",
 ["Language"]="English",
 ["Metascore"]="N/A",
 ["Plot"]="A dissenting juror in a murder trial slowly manages to convince the others that the case is not as obviously clear as it seemed in court.",
 ["Poster"]="http://ia.media-imdb.com/images/M/MV5BODQwOTc5MDM2N15BMl5BanBnXkFtZTcwODQxNTEzNA@@._V1_SX300.jpg",
 ["Rated"]="NOT RATED",
 ["Released"]="1 Apr 1957",
 ["Response"]="True",
 ["Runtime"]="96 min",
 ["Title"]="12 Angry Men",
 ["Type"]="movie",
 ["Writer"]="Reginald Rose (story)",
 ["Year"]="1957",
 ["imdbID"]="tt0050083",
 ["imdbRating"]="8.9",
 ["imdbVotes"]="367967",
}

使用 lua 语法可以轻松访问每个单独的值table[key]。以下是完整代码:

\usemodule[json]
\startluacode
  local tolua = utilities.json.tolua
  local fetch = resolvers.schemes.fetchstring
  local format = string.format
  local escape = url.escape

  thirddata = thirdata or {}
  function thirddata.imdbinfo(title)
    local url    = format("http://www.omdbapi.com/?t=%s", escape(title))
    local info   = fetch(url)
    local parsed = tolua(info)
    local value  = ""

    if parsed then
      value = format("[%s] %s (%s)", 
                     parsed["imdbRating"],
                     parsed["Title"],
                     parsed["Year"])
    else
      value = format("%s not found on imdb", title)
    end
    context(value)
  end
\stopluacode

\define[1]\getFilm{\ctxlua{thirddata.imdbinfo("#1")}}

\starttext
\startlines
\getFilm{8MM}
\getFilm{12 Angry Men}
\stoplines
\stoptext

这使

在此处输入图片描述

我没有费心去获取正确的格式,可以轻松添加。

原则上,上述代码也应该适用于LuaLaTeX,但我不知道是否有与ConTeXt的l-url.luautl-json.lua库等效的LuaLaTeX。

编辑:功能稍有改变imdbinfo,也可以获得海报。

\startluacode
  local tolua = utilities.json.tolua
  local fetch = resolvers.schemes.fetchstring
  local format = string.format
  local escape = url.escape

  thirddata = thirdata or {}
  function thirddata.imdbinfo(title)
    local url    = format("http://www.omdbapi.com/?t=%s", escape(title))
    local info   = fetch(url)
    local parsed = tolua(info)
    local value  = ""
    local poster = ""

    if parsed then
      value = format("[%s] %s (%s)", 
                     parsed["imdbRating"],
                     parsed["Title"],
                     parsed["Year"])
      poster = format("\\externalfigure[%s][width=0.3\\textwidth]",
                     parsed["Poster"])
    else
      value = format("%s not found on imdb", title)
    end
       context.startcontent()
          context(poster)
       context.stopcontent()
       context.startcaption()
          context(value)
        context.stopcaption()
  end
\stopluacode

\define[1]\getFilm{\ctxlua{thirddata.imdbinfo("#1")}}

\starttext
\startcombination[2]
\getFilm{8MM}
\getFilm{12 Angry Men}
\stopcombination

\stoptext

这使

在此处输入图片描述

因为我直接使用\externalfigure[url],所以海报只下载一次,然后结果被缓存1天。

相关内容