使用pdftools包获取pdfs的数据
笔者邀请您,先思考:
1 如何解析PDF数据?
通常情况下数据都包装在pdfs里面,值得庆幸的是有很多途径可以从中提取出数据。一个非常好的包就是pdftools(Github link),这篇博客将描述该包的一些基本函数。
首先,我们寻找一些包含有趣信息的pdf文件。为了完成目标,我使用世界健康组织整理的国家糖尿病数据概要。你可以在这里找到这些文件。如果你打开其中一个pdf文件,你将看到这样的信息:

我比较关心的是表格中间的这部分内容:

我想从不同国家获取这部分数据,把它们放进一个有条理的数据框,并且制作简单的绘图。首先从下载需要的程序包开始:
1library("pdftools")
2library("glue")
3library("tidyverse")
1## ── Attaching packages ────────────────────────────────────────────────────────────────────────────── tidyverse 1.2.1 ──
2## ✔ ggplot2 2.2.1 ✔ purrr 0.2.5
3## ✔ tibble 1.4.2 ✔ dplyr 0.7.5
4## ✔ tidyr 0.8.1 ✔ stringr 1.3.1
5## ✔ readr 1.1.1 ✔ forcats 0.3.0
6## ── Conflicts ───────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
7## ✖ dplyr::collapse() masks glue::collapse()
8## ✖ dplyr::filter() masks stats::filter()
9## ✖ dplyr::lag() masks stats::lag()`
1library("ggthemes")
2country <- c("lux", "fra", "deu", "usa", "prt", "gbr")
3url <- "http://www.who.int/diabetes/country-profiles/{country}_en.pdf?ua=1"
前面四行下载了该练习所需的程序包:pdftools是我在本帖开始描述的那个包,glue可作为一种替换paste()和paste0()函数的更好选择。认真观察url,你就会发现我写的是{country}。这不在原始链接中;原始链接应该是这样(以USA为例子):
1"http://www.who.int/diabetes/country-profiles/usa_en.pdf?ua=1"
因为我对几个国家感兴趣,所以我创建了一个向量包含了我感兴趣的国家代码。现在,使用glue()函数,奇妙的事情发生了:
1(urls <- glue(url))
这语句创建了一个向量包含了所有的链接,其中的{country}被变量country里面的代码逐一替换。
我采用一样的方法创建一系列pdf的名字用于我将要下载的文件。
1pdf_names <- glue("report_{country}.pdf")
现在我可以下载它们了:
1walk2(urls, pdf_names, download.file, mode = "wb")
walk2()是purrr包里面跟map2()类似的函数。你可以使用map2(),但walk2()用在这里更清洁,因为download.file()是对下载文件产生一定副作用的函数;map2()用于没有副作用的函数。
现在,我终于可以使用pdftools包里面的pdf_text()函数提取pdf文件里面的文本信息:
1raw_text <- map(pdf_names, pdf_text)
raw_text是一系列来自pdf文件的文本元素。可以看一下它的内容:
1str(raw_text)
再看看这些元素中的一个,仅仅是一长串字符:
1raw_text[[1]]
1## [1] "Luxembourg Total population: 567 000n Income group: HighnMortalitynNumber of diabetes deaths Number of deaths attributable to high blood glucosen males females males femalesnages 30–69 <100 <100 ages 30–69 <100 <100nages 70+ <100 <100 ages 70+ <100 <100nProportional mortality (% of total deaths, all ages) Trends in age-standardized prevalence of diabetesn Communicable,n maternal, perinatal Injuries 35%n and nutritional 6% Cardiovascularn conditions diseasesn 6% 33%n 30%n 25%n % of populationn Other NCDsn 16% 20%n No data available 15% No data availablen Diabetes 10%n 2%n 5%n Respiratoryn diseasesn 6% 0%n Cancersn 31%n males femalesnPrevalence of diabetes and related risk factorsn males females totalnDiabetes 8.3% 5.3% 6.8%nOverweight 70.7% 51.5% 61.0%nObesity 28.3% 21.3% 24.8%nPhysical inactivity 28.2% 31.7% 30.0%nNational response to diabetesnPolicies, guidelines and monitoringnOperational policy/strategy/action plan for diabetes NDnOperational policy/strategy/action plan to reduce overweight and obesity NDnOperational policy/strategy/action plan to reduce physical inactivity NDnEvidence-based national diabetes guidelines/protocols/standards NDnStandard criteria for referral of patients from primary care to higher level of care NDnDiabetes registry NDnRecent national risk factor survey in which blood glucose was measured NDnAvailability of medicines, basic technologies and procedures in the public health sectornMedicines in primary care facilities Basic technologies in primary care facilitiesnInsulin ND Blood glucose measurement NDnMetformin ND Oral glucose tolerance test NDnSulphonylurea ND HbA1c test NDnProcedures Dilated fundus examination NDnRetinal photocoagulation ND Foot vibration perception by tuning fork NDnRenal replacement therapy by dialysis ND Foot vascular status by Doppler NDnRenal replacement therapy by transplantation ND Urine strips for glucose and ketone measurement NDnND = country did not respond to country capacity surveyn〇 = not generally available ● = generally availablenWorld Health Organization – Diabetes country profiles, 2016.n"
如你所看的那样,这是一串非常长的字符,包含了很多换行符(”n”)。首先,我们需要根据”n”分离字符串。其次,或许很难看到,但表格以字串“Prevalence of diabetes”开始,并且以“National response to diabetes”结束。接着,我们需要从文本中提取国家名,把它们放在同一列。你将看到,一系列的操作是必须的,因此,我将把所有针对raw_text的操作放在一个函数里面。
1clean_table <- function(table){
2 table <- str_split(table, "n", simplify = TRUE)
3 country_name <- table[1, 1] %>%
4 stringr::str_squish() %>%
5 stringr::str_extract(".+?(?=\sTotal)")
6 table_start <- stringr::str_which(table, "Prevalence of diabetes")
7 table_end <- stringr::str_which(table, "National response to diabetes")
8 table <- table[1, (table_start +1 ):(table_end - 1)]
9 table <- str_replace_all(table, "\s{2,}", "|")
10 text_con <- textConnection(table)
11 data_table <- read.csv(text_con, sep = "|")
12 colnames(data_table) <- c("Condition", "Males", "Females", "Total")
13 dplyr::mutate(data_table, Country = country_name)
14}
我建议你把所有这些操作都过一遍并且理解每一步的含义。但是,我只描述其中的某些部分,比如以下这个:
1stringr::str_extract(".+?(?=\sTotal)")
它使用了一种非常奇异的规则表述:“.+?(?=sTotal)”。它提取字串“Total”前隔着空格的任何成分。这是因为包含国家名字的第一行是这样的:“Luxembourg Total population: 567 000n”。所以“Total”前隔着空格的任何成分就是国家名字。这就是它们所在的行。
1table <- str_replace_all(table, "\s{2,}", "|")
2text_con <- textConnection(table)
3data_table <- read.csv(text_con, sep = "|")
第一行取代2个空格或者更多(”s{2,}”)”|”.我这样做的原因是,可以通过识别分隔符“|”在R里面以数据框的形式读取表格的信息。第二行,我把table作为一个文本连接,以便可以在R通过read.csv()的方式读取。倒数第二行,我改变了列名,并且在数据框加入了“Country”新列。
现在,我可以把这个函数应用到一系列pdf的原始文本中:
1diabetes <- map_df(raw_text, clean_table) %>%
2 gather(Sex, Share, Males, Females, Total) %>%
3 mutate(Share = as.numeric(str_extract(Share, "\d{1,}\.\d{1,}")))
我用gather()改变数据的排列(可以比较改变前后的数据形式)。接着,将“Share”列转换为数值型(它可以把“12.3%”变成12.3)并把它们绘制成图。首先看看数据:
1diabetes
现在可以绘图了
1ggplot(diabetes) + theme_fivethirtyeight() + scale_fill_hc() +
2 geom_bar(aes(y = Share, x = Sex, fill = Country),
3 stat = "identity", position = "dodge") +
4 facet_wrap(~Condition)

以上就是绘制该图的一系列工作!
作者:Bruno Rodrigues
原文链接:https://www.brodrigues.co/blog/2018-06-10-scraping_pdfs/
内容推荐
数据人网:数据人学习,交流和分享的平台,诚邀您创造和分享数据知识,共建和共享数据智库。
请关注“恒诺新知”微信公众号,感谢“R语言“,”数据那些事儿“,”老俊俊的生信笔记“,”冷🈚️思“,“珞珈R”,“生信星球”的支持!