gunhawk

gunhawk

Frontend Developer

Coding is part of my life, 加藤恵は大好き=。=

用python3开发爬虫的一些总结

作者: gunhawk时间: 2017-10-30python

使用的库

  • requests python的http请求库
  • beautifulSoup4 html解析库

设置默认的请求参数

request库的API用的是指针字典作为形参, 可以传入无序的参数, 我最初的想法是内置一些默认参数, 可以通过外部API传参改变默认参数, 就像jquery插件一样, 由于考虑到默认参数部分value也是一个字典(如headers), 试了下deepcopy方法好像也不能达到我的效果, 于是我自己在一个类里撸了一个set_default的方法

def set_default(self, **kwargs):
    headers = {
        'Content-Type': 'application/json; charset=UTF-8',
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36'
    }
    if 'headers' in kwargs:
        for key, value in headers.items():
            if key not in kwargs['headers']:
                kwargs['headers'][key] = value
    else:
        kwargs['headers'] = headers
    return kwargs

然后在请求前先执行set_default方法:

def post(self, url, **kwargs):
    kwargs = self.set_default(**kwargs)
    r = requests.post(url, **kwargs)
    return self.callback(r)

请教了一下python的前辈, 更优雅的方法可以试试python的装饰器

关于beautifulSoup的解析

  1. 如果一个标签内含有子标签, find_next方法会先找标签内的子元素而非其同级的下一个标签, 这点跟DOM解析不一样(也可能是我理解错了find_next方法 :(
  2. 如果要获取一个标签内的background-url, 是通过data-src获取, 如elem['data-src']

关于请求异常的细节处理

这里先mark一下, 以后研究一下 :P

使用多线程收集数据

我第一个爬虫试水的网站是我很喜欢的蜜柑计划, 包含2013-2017年的番组, 而且对每个番组要进行二次请求才能获取字幕组的信息. 单个线程有点慢(首页全数据收集需要580+秒. 于是我简单地根据番组年份分了对应的线程来收集

def find_year_lists(self, soup):
    year_lists = soup.find_all(class_='dropdown-submenu')
    start_time = time.time()
    for year_list in year_lists:
        year = year_list.find_next('a').string
        self.torrents[year] = {}
        worker = threading.Thread(
            name='thread-' + year, target=self.find_season_lists, args=(year_list, self.torrents[year])
        )
        self.threads.append(worker)
        worker.start()
    list(map(lambda worker: worker.join(), self.threads))
    list(map(lambda year_list: self.output(year_list.find_next('a').string), year_lists))

换了多线程后, 首页数据收集仅仅用了170+秒, 红色有角3倍速有无有哇!!

threading.Threadargs必须传一个元祖, 而且如果target函数的形参只有1个, args要写成

worker = threading.Thread(name=threading.current_thread().getName() + '-' + o['subname'], target=self.find_sub_id, args=(o,))

值得注意的是, python的map函数仅仅是返回一个map对象的迭代器, 要进一步调用才会去遍历, 而且要在map方法里直接写匿名函数的话, 只能写一行, 否则只能再定义一个函数作为形参传递, 这里不得不说js大法好!

关于json.dumps

python内置序列化json的方法json.dumps执行后会把中文转成unicode, 这样我进行文件写入的时候全TM都是unicode, 压根就不能看好伐! 因此我用node把含有unicode的文件读出来, 解析json后再次序列化, 重新写入文件, 太鸡汁啦嘎哈哈~!!