爬虫专题

Python3 搭建免费代理池

2018-12-07  本文已影响1人  未不明不知不觉

最近需要抓取一些数据,但是苦于没有代理,之前写过一个简单的代理,最近重构一下,这里写下思路,希望大家多提意见

基本思路

思路很简单,我就不画框架图了:

1. 从免费代理网站获取免费代理
2. 对免费代理进行校检存储
3. 愉快的开始使用吧

开发环境和开发包

软件

  aiohttp
  dummy_useragent
  aredis
  logzero
  cuckoopy

项目结构

使用tree /F 来获取当前的目录结构,得到下面的输出

C:.
│  .gitignore
│  LICENSE
│  README.rst
│  requirement.txt
│  setup.py 
│
├─freeproxy_cn
│  │  __init__.py
│  │
│  ├─core
│  │      channel.py
│  │      engine.py
│  │      http.py
│  │      __init__.py
│  │
│  ├─site
│  │      cool.py
│  │      crossin.py
│  │      eight9.py
│  │      I337.py
│  │      ip3366.py
│  │      iphai.py
│  │      ipjiang.py
│  │      kuai.py
│  │      proxydocker.py
│  │      seofang.py
│  │      super.py
│  │      threeone.py
│  │      xiaosu.py
│  │      xici.py
│  │      xroxy.py
│  │      __init__.py
│  │
│  ├─site2
│  │      freecz.py
│  │      idcloak.py
│  │      myproxy.py
│  │      nova.py
│  │      __init__.py
│  │
│  └─util
│          pipe.py
│          __init__.py
│
└─test
        test_bug.py
        test_hkong.py
        test_site.py
        __init__.py

核心代码

在抓取的过程中我发现,很多代理的网站存储是一个表格结构,每个table的第一个tr为表头可以忽略,


示例一 示例二

但是我们可以观察到代理的host和port的位置可能不同,为了提取代理的host和port,我们可以用下面的代码提取

# channel.py

class Channel(object):
    def __init__(self, proxy=None, *arg, **kwargs):
        self.http = Http()
        self.start_pos = 2
        self.td_idx = [1, 2]
        
    async def handle(self, url):
            doc = await self.http.get(url) >> to_doc
            items = doc.xpath("//table//tr[position()>=%s]" % self.start_pos)
            proxies = []
            for item in items:
                try:
                    host = item >> extra_head(
                        "./td[position()=%s]//text()" % self.td_idx[0])
                    port = item >> extra_head(
                        "./td[position()=%s]//text()" % self.td_idx[1])
                except Exception:
                    continue
                if len(port) > 5:
                    continue
                proxies.append((host, port))

另外有些网站的有抓取模板,我们可能需要抓取免费代理的前几页,例如对于西刺

# xici.py
class XiCi(Channel):
    def __init__(self):
        super(XiCi, self).__init__()
        self.name = "xici"
        self.url_plt = [
            "http://www.xicidaili.com/wn/%s",
            "http://www.xicidaili.com/wt/%s",
            "http://www.xicidaili.com/nn/%s",
            "http://www.xicidaili.com/nt/%s",
        ]

        self.td_idx = [2, 3]

    async def boostrap(self):
        urls = []
        for i in range(1, 3):
            urls += [plt % i for plt in self.url_plt]
        self.funcmap = {self.handle: urls}

在上面的boostrap函数里面我们初始化了抓取url的列表,默认为抓代理的前两页,至于funcmap下面会用到

http模块的useragent我使用了自己写的一个dummy_useragent包,和faker_useragent类似,但是省略了初始化阶段(网络问题,国内使用faker_useragent初始化总会报错)

engin是代理抓取调度的核心,核心代码如下

#engin.py

class Engin(object):
        async def _run(self):
        tasks = []
        for site in self.sites:
            tasks.append(asyncio.ensure_future(self.site_run(site)))
        await asyncio.gather(*tasks)

    async def site_run(self, site):
        async with aiohttp.ClientSession() as session:
            site.set_http(Http(session))
            logger.debug("start grab site {}".format(site.name))
            await site.boostrap()
            funcs = site.funcmap.keys()
            for zp in zip(*site.funcmap.values()):
                for func_param in zip(funcs, zp):
                    func, param = func_param
                    coro = func(param)
                    await coro
                    await asyncio.sleep(2)  # 并发抓取 容易封禁ip

    async def run(self):
        while True:
            logger.debug("开始新一轮的抓取")
            await self._run()
            await asyncio.sleep(60 * 20)

上面代码很简单,主要是20分钟抓取一次代理,对于每个网站的抓取间隔为两秒,因为抓取使用的是本地 IP,并发太快会被封。这个地方可以改进:

  1. 若没有代理池,还是需要抓取间隔
  2. 若已抓取到一部分代理,我们可以利用抓取到的代理进行并发,取消抓取间隔

项目地址

代码很简单,也有很多不足之处,适合初学者练手,一起学习交流。求start ⭐

上一篇下一篇

猜你喜欢

热点阅读