-
Notifications
You must be signed in to change notification settings - Fork 13
/
scrap.jl
186 lines (147 loc) · 5.64 KB
/
scrap.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
#A partir de PDFs con tablas de la SSA, devuelve un CSV.
#Paquetería para leer pdfs y manipular fechas
using PDFIO
using Dates
using DelimitedFiles
using ArgParse
#Devuelve el texto del pdf como un string
function texto_pdf(archivo)
#Abre el documento.
documento = pdDocOpen(archivo)
#Genera objetos que representan las páginas.
pages = pdDocGetPageRange(documento, 1:pdDocGetPageCount(documento))
#Creamos un buffer para compilar los datos.
datos = IOBuffer()
#Escribimos el texto al buffer.
#Entre cada página forzamos una línea nueva.
for page in pages
pdPageExtractText(datos, page)
write(datos, " \n")
end
#Cerramos el documento.
pdDocClose(documento)
#Devolvemos el texto en un string.
return String(take!(datos))
end
#Separa el string por líneas y elimina aquellas que no corresponden a casos.
function eliminar_nocasos(string)
#Separa por línea
rows = split(string, "\n")
#Revisa que el inicio de la línea sea de la forma {espacios}Número y descarta la línea si no.
filter!(row -> occursin(r"^ +\d", row), rows)
#Transforma los substrings en strings:
rows = String.(rows)
return rows
end
function procesa_fecha(string)
try
return Date(string, "dd/mm/yyyy") |> Base.string
catch ArgumentError
if length(string) == 5
#Son fechas representadas en un formato de Excel: con el conteo de días iniciando en 1900-01-01. (Día 1)
#https://www.covid19in.mx/docs/datos/tablas-casos/normalizacion/fecha/
#Sin embargo, por retrocompatibilidad, se considera erróneamente que 1900 es un año bisiesto,
#por lo que para convertir las fechas a partir del 1900-02-28 se requiere restar dos al número mostrado,
#una unidad para tener un intervalo de tiempo respecto de 1900-01-01 y otra unidad para corregir por el día
#extra considerado.
#Gracias a Juan Claudio Toledo Roy por estas correciones.
Δt = parse(Int, string) - 2 #Nuestras fechas son posteriores a 1900-02-28.
return Date("1900-01-01") + Day(Δt) |> Base.string
else
return ""
end
end
end
function procesa_fila(string, index_fechas)
out = replace(string, r"^\s+" => "") # espacios al inicio
out = replace(out, r"\s+$" => "") # espacios al final
# Correciones para normalizar una tabla como la de Abril 6
correcciones = Dict("DISTRITO FEDERAL" => "CIUDAD DE MÉXICO",
"MEXICO" => "MÉXICO",
"MICHOACAN" => "MICHOACÁN",
"NUEVO LEON" => "NUEVO LEÓN",
"QUERETARO" => "QUERÉTARO",
"SAN LUIS POTOSI" => "SAN LUIS POTOSÍ",
"YUCATAN" => "YUCATÁN",
"MASCULINO" => "M",
"FEMENINO" => "F")
for (k, v) in correcciones
out = replace(out, k => v)
end
out = split(out, r"\s{2,}") # mas de dos espacios define las entradas
for i in index_fechas
out[i] = procesa_fecha(out[i]) # fechas en formato ISO (de ser posible)
end
return out
end
#Función principal que toma el pdf y escribe csv correspondiente
function scraping(archivo_pdf, archivo_csv;
procedencia=false,
fecha_llegada=false,
index_fechas=[5])
#Obtenemos los casos en un array:
casos = procesa_fila.(eliminar_nocasos(texto_pdf(archivo_pdf)), Ref(index_fechas))
# Ref evita el broadcasting con el segundo argumento (la lista de las columnas
# que se tienen que considerar fechas)
header = ["id", "estado", "sexo", "edad", "fecha_sintomas",
"confirmacion"] # "procedencia", fecha_llegada
# Esto se puede cambiar para modificar el header
procedencia ? push!(header, "procedencia") : nothing
fecha_llegada ? push!(header, "fecha_llegada") : nothing
#Escribe el archivo
open(archivo_csv, "w") do io
writedlm(io, [header], ',')
writedlm(io, casos, ',') # esto escribe todas las filas con separadores y \n
end
return "Done"
end
# Si el nombre del csv no se proporciona, se utiliza la misma base
function scraping(archivo_pdf; kwargs...)
archivo_csv = splitext(basename(archivo_pdf))[1] * ".csv"
scraping(archivo_pdf, archivo_csv; kwargs...)
end
function parse_commandline()
settings = ArgParseSettings("convierte PDF con casos de covid-19 a archivo csv",
suppress_warnings=true)
@add_arg_table! settings begin
"pdf"
help = "el archivo PDF con la tabla"
required = true
"--outfile", "-o"
help = "el archivo csv donde se va a escribir el resultado (default: el nombre del PDF)"
arg_type = String
default = ""
"--procedencia"
help = "utilizar la columna `Procedencia` (default: falso)"
action = :store_true
default = false
"--llegada"
help = "utilizar la columna `Fecha de llegada a México` (default: falso)"
action = :store_true
default = false
end
return parse_args(ARGS, settings)
end
function main()
parsed_args = parse_commandline()
archivo_pdf = parsed_args["pdf"]
@assert endswith(archivo_pdf, ".pdf")
archivo_csv = parsed_args["outfile"]
procedencia = parsed_args["procedencia"]
fecha_llegada = parsed_args["llegada"]
kwargs = Dict{Symbol,Any}()
kwargs[:procedencia] = procedencia
kwargs[:fecha_llegada] = fecha_llegada
# trata de adivinar que fecha_llegada es una columna de fecha
fecha_llegada ? kwargs[:index_fechas] = [5,8] : nothing
if length(archivo_csv) == 0
status = scraping(archivo_pdf; kwargs...)
else
@assert endswith(archivo_csv, ".csv")
status = scraping(archivo_pdf, archivo_csv; kwargs...)
end
println(status)
end
if abspath(PROGRAM_FILE) == @__FILE__
main()
end