网页抓取:根据结果展开/收缩边界框

时间:2016-02-14 05:00:57

标签: python for-loop recursion bounding-box

一位客户想知道他们竞争对手商店的位置,所以我是准邪恶的并抓住竞争对手的网站。

服务器接受边界框(即左下角和右上角坐标)作为参数,并返回在边界框内找到的位置。这部分工作正常,我可以在给定边界框的情况下成功检索商店位置。

问题是仅返回边界框中的前10个位置 - 因此在填充区域中,10度边界框将返回太多位置:

enter image description here

我总是可以使用较小的边界框,但我正在尝试避免对服务器进行不必要的点击,同时确保返回所有商店。

因此,我需要一种方法来减少搜索矩形大小,当找到10个商店时(因为可能存在超过10个商店),并使用较小的搜索矩形大小递归搜索,然后恢复为更大的矩形下一个网格单元。

我编写了一个函数,它在给定边界框的情况下从服务器检索存储:

stores = checkForStores(<bounding box>)
if len(stores) >= 10:
  # There are too many stores. Search again with a smaller bounding box
else:
  # Everything is good - process these stores

但我正在努力设置checkForStores函数的相应边界框。

我尝试使用纬度和经度上的for循环设置主网格单元格:

cellsize = 10
for minLat in range(-40, -10, cellsize):
    for minLng in range(110, 150, cellsize):
        maxLat = minLat + cellsize
        maxLng = minLng + cellsize

...但我不知道如果找到10个商店,如何继续使用较小的边界框进行搜索。我也尝试使用while循环,但我无法让它们中的任何一个工作。

感谢您提供有关从何处着手的建议或指示。

1 个答案:

答案 0 :(得分:5)

以下是使用递归的方法。代码应该是不言自明的,但这是它的工作原理: 给出一些边界框,它检查其中的商店数量,如果有多于或等于10,则将该框划分为较小的框并用每个新的边界框调用自身。这样做直到找到不到10家商店。在这种情况下,找到的商店只是保存在列表中。

注意:由于使用了递归,因此可能会出现超出最大递归深度的情况。这是理论上的。在你的情况下,即使你通过40 000 x 40 000 km的边界框,也只需要15步就可以到达{1}}的1 x 1 km边界框:

cell_axis_reduction_factor=2

无论如何,在这种情况下,您可以尝试增加In [1]: import math In [2]: math.log(40000, 2) Out[2]: 15.287712379549449 个数字。

另请注意:在Python中,根据PEP 8,函数应为小写,并带有下划线,因此我将cell_axis_reduction_factor函数重命名为checkForStores

check_for_stores

以下是输出示例:

# Save visited boxes. Only for debugging purpose.
visited_boxes = []


def check_for_stores(bounding_box):
    """Function mocking real `ckeck_fo_stores` function by returning
    random list of "stores"
    """
    import random
    randint = random.randint(1, 12)
    print 'Found {} stores for bounding box {}.'.format(randint, bounding_box)
    visited_boxes.append(bounding_box)
    return ['store'] * randint


def split_bounding_box(bounding_box, cell_axis_reduction_factor=2):
    """Returns generator of bounding box coordinates splitted
    from parent `bounding_box`

    :param bounding_box: tuple containing coordinates containing tuples of
          lower-left and upper-right corner coordinates,
          e.g. ((0, 5.2), (20.5, 14.0))
    :param cell_axis_reduction_factor: divide each axis in this param,
                                       in order to produce new box,
                                       meaning that in the end it will
                                       return `cell_axis_reduction_factor`**2 boxes
    :return: generator of bounding box coordinates

    """
    box_lc, box_rc = bounding_box
    box_lc_x, box_lc_y = box_lc
    box_rc_x, box_rc_y = box_rc

    cell_width = (box_rc_x - box_lc_x) / float(cell_axis_reduction_factor)
    cell_height = (box_rc_y - box_lc_y) / float(cell_axis_reduction_factor)

    for x_factor in xrange(cell_axis_reduction_factor):
        lc_x = box_lc_x + cell_width * x_factor
        rc_x = lc_x + cell_width

        for y_factor in xrange(cell_axis_reduction_factor):
            lc_y = box_lc_y + cell_height * y_factor
            rc_y = lc_y + cell_height

            yield ((lc_x, lc_y), (rc_x, rc_y))


def get_stores_in_box(bounding_box, result=None):
    """Returns list of stores found provided `bounding_box`.

    If there are more than or equal to 10 stores found in `bounding_box`,
    recursively splits current `bounding_box` into smaller one and checks
    stores in them.

    :param bounding_box: tuple containing coordinates containing tuples of
          lower-left and upper-right corner coordinates,
          e.g. ((0, 5.2), (20.5, 14.0))
    :param result: list containing found stores, found stores appended here;
                   used for recursive calls
    :return: list with found stores

    """
    if result is None:
        result = []

    print 'Checking for stores...'
    stores = check_for_stores(bounding_box)
    if len(stores) >= 10:
        print 'Stores number is more than or equal 10. Splitting bounding box...'
        for splitted_box_coords in split_bounding_box(bounding_box):
            get_stores_in_box(splitted_box_coords, result)
    else:
        print 'Stores number is less than 10. Saving results.'
        result += stores

    return result


stores = get_stores_in_box(((0, 1), (30, 20)))
print 'Found {} stores in total'.format(len(stores))
print 'Visited boxes: '
print visited_boxes