En el shell Scrapy para el sitio web http://www.apkmirror.com/apk/google-inc/sheets/sheets-1-7-152-06-release/ google-sheet-1-7-152-06-30-android-apk-download /, estoy tratando de analizar el version_name (1.7.152.06.30) y version_code (7152063) (vea la captura de pantalla a continuación) de una manera sucinta utilizando Scrapy's procesador MapCompose.

enter image description here

Mi primer paso es obtener todo el texto de la sección 'Detalles de APK':

In [2]: apk_details = response.xpath('//*[@title="APK details"]/following-sibling::*[@class="appspec-value"]//text()').extract()

Sobre el cual apk_details es una lista de la siguiente manera:

[u'Version: 1.7.152.06.30 (71520630)',
 u'arm ',
 u'Package: com.google.android.apps.docs.editors.sheets',
 u'\n',
 u'191 downloads ']

He definido las siguientes funciones auxiliares:

import re

def get_version_line(apk_details):
    '''Get the line containing the version from the 'APK details' section.'''
    return next(line for line in apk_details if line.startswith("Version:"))

def parse_version_line(version_line):
    '''Parse the 'versionName' and 'versionCode' from the relevant line in 'APK details'.'''
    PATTERN = r"^Version: (?P<version_name>.+) \((?P<version_code>\d+)\)\s*$"       # Note that the pattern includes the end-of-line character ($). This is necessary because some package names (e.g. Google Play) themselves contain brackets.
    return re.match(PATTERN, version_line).groupdict()

Tal que el version_name se puede obtener de la siguiente manera:

In [4]: version_line = get_version_line(apk_details)

In [5]: version_line
Out[5]: u'Version: 1.7.152.06.30 (71520630)'

In [6]: groups = parse_version_line(version_line)

In [7]: groups
Out[7]: {'version_code': u'71520630', 'version_name': u'1.7.152.06.30'}

In [8]: version_name = groups.get("version_name")

In [9]: version_name
Out[9]: u'1.7.152.06.30'

En otras palabras, me gustaría aplicar get_version_line, parse_version_line y lambda d: d.get("version_name") sucesivamente a apk_details. Sin embargo, si intento lo siguiente:

In [10]: proc = MapCompose(get_version_line, parse_version_line)

In [11]: proc(apk_details)

Recibo una excepción StopIteration:

---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-11-59a0bd60721d> in <module>()
----> 1 proc(apk_details)

/usr/local/lib/python2.7/dist-packages/scrapy/loader/processors.pyc in __call__(self, value, loader_context)
     26             next_values = []
     27             for v in values:
---> 28                 next_values += arg_to_iter(func(v))
     29             values = next_values
     30         return values

/home/kurt/dev/apkmirror_scraper/apkmirror_scraper/items.pyc in get_version_line(apk_details)
     33 def get_version_line(apk_details):
     34     '''Get the line containing the version from the 'APK details' section.'''
---> 35     return next(line for line in apk_details if line.startswith("Version:"))
     36 
     37 def get_architectures_line(apk_details):

StopIteration:

¿Cómo usaría correctamente MapCompose en este caso?

1
Kurt Peek 25 abr. 2017 a las 14:07

2 respuestas

La mejor respuesta

Use Compose en su lugar con la bandera re.MULTILINE:

import re
from scrapy.loader.processors import Compose


def parse_version_line(version_line):
    """Parse the 'versionName' and 'versionCode' from the relevant line in 'APK details'."""
    text = '\n'.join(version_line)
    PATTERN = r"^Version: (?P<version_name>.+) \((?P<version_code>\d+)\)\s*$"  # Note that the pattern includes the end-of-line character ($). This is necessary because some package names (e.g. Google Play) themselves contain brackets.
    return re.match(PATTERN, text, re.MULTILINE).groupdict()

Probándolo:

data = [u'Version: 1.7.152.06.30 (71520630)',
        u'arm ',
        u'Package: com.google.android.apps.docs.editors.sheets',
        u'\n',
        u'191 downloads ']
m = Compose(parse_version_line)
print(m(data))
# {'version_name': u'1.7.152.06.30', 'version_code': u'71520630'}
1
Granitosaurus 25 abr. 2017 a las 11:38

Basado en la respuesta de Granitosaurus, descubrí que la solución era simplemente usar Scrapy's Compose en lugar de MapCompose:

In [26]: proc = Compose(get_version_line, parse_version_line, lambda d: d.get("version_name"))

In [27]: print proc(apk_details)
1.7.152.06.30

Después de leer más detenidamente la documentación, esto tiene sentido: en términos generales, Compose genera la composición de las funciones dadas en la entrada 'completa', mientras que MapCompose realiza la función compuesta en cada elemento.

0
Community 23 may. 2017 a las 12:02