尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
C语言网页爬虫实战:从socket到Windows独立exe
简介面向C语言学习者的网页爬虫项目包内含网络请求库实现的爬虫源码、可直接运行的视窗系统可执行程序、命令行传输工具、根证书文件以及自动构建脚本压缩包约1.08MB共5个文件。项目完整演示了如何用C语言完成网址解析、请求发送、响应接收与内容提取根证书文件用于加密连接时的服务器身份验证防止数据被篡改构建脚本则展示了在视窗环境下使用开源工具链自动编译源码的完整流程。其中可执行文件适合快速上手源码文件适合深入学习证书与脚本解决了实际运行中的安全和构建问题。已有2208人学习下载适合希望将C语言与网络编程结合、理解爬虫底层原理的开发者参考。通过阅读源码可掌握常用网络接口的调用方式与交互细节直接运行已编译程序可快速观察抓取效果修改并重新编译还能体会从源码到可执行程序的完整过程为后续扩展多页面遍历、链接去重、关键词筛选等功能打下扎实基础。1. C语言网页爬虫为什么在脚本泛滥的年代还要编译一个exe很多从业者看到“C语言爬虫”第一反应是用Python不香吗但在Windows生产环境里你经常会遇到一种尴尬——目标机器没有Python、没有Node甚至不让装运行时却催着你要一份网页数据。这时候一个编译好的exe就是最省事的存在双击能跑命令行能调丢给谁都不会缺依赖。C语言网页爬虫的价值不在于“能不能爬到”而在于它把网络请求、HTTP解析和内存管理全部摊在眼前让你看到爬虫最本质的样子。这篇笔记会从原理、代码、编译版用法到排错一路讲完适合想在Windows环境下稳定抓取网页、又不想被黑匣子绑架的人。2. 先立住理论基础HTTP在C语言眼里到底是什么2.1 网页抓取的三步建连、发请求、切响应爬虫的核心从来不是正则或XPath而是先把网页从服务器“拿下来”。用C语言写爬虫时整个过程会被压缩成三个非常原始的步骤第一步建立一个TCP连接到目标服务器的80端口第二步向这个连接写入一段HTTP请求文本第三步把服务器返回的数据读出来然后手动找到响应头和响应体的分界线。这里的关键认知是HTTP协议本身就是一个文本协议。你发给服务器的请求长成这样GET /index.html HTTP/1.1 Host: www.example.com Connection: close注意最后必须有一个空行这个空行告诉服务器“请求头结束了后面没有请求体”。服务器返回的响应也是同样的结构先是状态行和一堆响应头然后是一个空行再往后才是HTML正文。C语言干的事情说穿了就是发送这段文本、接收返回文本、按空行切出正文。2.2 选型手写winsock、WinINet还是libcurl在Windows上做C语言网络请求你面前有三条路直接调用winsock2的socket接口用系统自带的WinINet/WinHTTP或者链接libcurl。三者的区别非常明显。方案依赖代码量可控性TLS支持winsock2手写仅系统自带的ws2_32稍多最高每一字节都看得见无WinINet/WinHTTP系统API较少中等细节被封装有libcurl需附带DLL或静态链接最少封装太多有我一般会选winsock2原因很实际这个爬虫要编译成独立的Windows可执行文件分发libcurl要么带一个DLL要么静态链接体积膨胀WinINet虽然能用但它的回调机制和缓存策略有时会把简单的事情搞复杂。手写socket看着原始但胜在零额外依赖、报错直接出了问题用抓包工具一对比就能定位。2.3 第一个最小可运行的socket请求代码为了让你先建立信心下面这段代码就是整个爬虫的地基——连接服务器、发GET请求、把响应打印出来。用MinGW编译时gcc记得加-lws2_32链接参数。#include winsock2.h #include ws2tcpip.h #include stdio.h #include string.h #pragma comment(lib, ws2_32.lib) int main(void) { WSADATA wsa; SOCKET sock; struct hostent *host; struct sockaddr_in addr; // 1. 初始化Windows套接字环境版本2.2 if (WSAStartup(MAKEWORD(2, 2), wsa) ! 0) { printf(WSAStartup failed\n); return 1; } // 2. 创建TCP套接字 sock socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sock INVALID_SOCKET) { printf(socket failed: %d\n, WSAGetLastError()); WSACleanup(); return 1; } // 3. 域名解析把域名转成IP地址 host gethostbyname(example.com); if (host NULL) { printf(gethostbyname failed\n); return 1; } addr.sin_family AF_INET; addr.sin_port htons(80); // htons保证端口字节序正确 memcpy(addr.sin_addr, host-h_addr, host-h_length); // 4. 连接服务器 if (connect(sock, (struct sockaddr *)addr, sizeof(addr)) SOCKET_ERROR) { printf(connect failed: %d\n, WSAGetLastError()); return 1; } // 5. 发送HTTP请求HTTP/1.0配合Connection: close让服务器尽早断开 const char *req GET / HTTP/1.0\r\n Host: example.com\r\n Connection: close\r\n\r\n; send(sock, req, (int)strlen(req), 0); // 6. 循环接收服务器数据直到对方关闭连接 char buf[4096]; int n; while ((n recv(sock, buf, sizeof(buf) - 1, 0)) 0) { buf[n] \0; printf(%s, buf); } closesocket(sock); WSACleanup(); return 0; }代码里的几个参数值得说清楚htons(80)是把端口号从主机字节序转成网络字节序这一步忘了写连接就会跑到错误的端口上recv的缓冲区设为4096是考虑到MTU通常不会超过这个量级但这不代表一次recv就能收到完整网页所以必须用while循环读到返回0或-1为止。这段代码是后续所有功能的基础先让它在你的机器上跑通再继续往下走。3. 把最小示例扩展成能用的爬虫完整实现与三个必调参数3.1 URL解析别让一个斜杠毁掉你的请求最小示例里的URL是写死的实际使用时你要从命令行传入http://ip:port/path这样的完整地址。手写解析时最容易出的问题有两个一是漏掉path比如访问http://example.com时应该请求GET /而不是GET二是没有处理端口默认80之外的端口必须在HTTP请求的Host头里带上。解析流程我会拆成三步先定位http://后面的部分再按冒号切端口最后按斜杠切path。#include stdio.h #include stdlib.h #include string.h // 解析URL输出主机名、端口和请求路径 // 返回0成功-1失败 int parse_url(const char *url, char *host, int *port, char *path) { const char *p url; // 跳过协议头 if (strncmp(p, http://, 7) 0) { p 7; } else { return -1; } // 截取主机部分 const char *host_end strchr(p, :); const char *path_start strchr(p, /); if (host_end (path_start NULL || host_end path_start)) { // 有冒号但没有路径端口在冒号后面 size_t len host_end - p; memcpy(host, p, len); host[len] \0; *port atoi(host_end 1); strcpy(path, /); } else { // 无端口主机到路径或结尾为止 const char *end path_start ? path_start : p strlen(p); size_t len end - p; memcpy(host, p, len); host[len] \0; *port 80; strcpy(path, path_start ? path_start : /); } return 0; }这段解析的核心逻辑是判断冒号出现在斜杠之前还是之后。http://example.com:8080/news这样的地址冒号在斜杠之前说明8080是端口如果写成http://example.com/news冒号不存在端口就必须默认80。解析完后请求行的组装要特别注意GET %s HTTP/1.1path必须以/开头否则服务器会返回400。3.2 响应头与正文切分找对那四个字节请求发出去之后返回的数据从第一个字节开始就不是正文最前面是状态行和响应头。响应头和正文之间用一个\r\n\r\n序列分隔这个只有四个字节的序列就是切分的关键。常见做法是把recv到的所有数据先累积到一个大缓冲区然后扫描缓冲区寻找\r\n\r\n找到的位置往后偏移4个字节就是正文起点。// 从响应缓冲区中提取正文 // buf是完整响应len是响应长度 // 返回正文起始下标找不到返回-1 int extract_body(const char *buf, int len) { for (int i 0; i len - 4; i) { if (buf[i] \r buf[i1] \n buf[i2] \r buf[i3] \n) { return i 4; } } return -1; }这里有个要注意的边界问题如果recv分多次到达上一次recv的缓冲区末尾可能只有半个\r\n\r\n。我的习惯是把整个响应累积到一个动态扩容的byte数组里每次recv完后就扫描一次找不到就继续等下一段数据直到找到分隔符或者连接关闭。千万不要每段数据都单独切分那样大概率会把HTML拦腰截断。3.3 三个必调参数超时、最大体量、编码转换把最小示例跑通之后真实环境一定会逼你调三个参数。第一个是socket超时——不设超时的话服务器半死不活时你的程序会僵在recv里无比被动。用setsockopt可以同时设接收和发送超时int timeout 10 * 1000; // 毫秒 setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char *)timeout, sizeof(timeout)); setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (const char *)timeout, sizeof(timeout));第二个是响应体大小上限。网页动辄几MB如果你在内存里无上限地累积一个OCR识别的超大文件就能让程序崩溃。我的做法是设定一个上限比如10MB超过就停止接收并报错在做分布式采集时这个值还承担着防止死循环的重要角色。第三个是编码问题。Windows的命令行默认GBK网页很多是UTF-8直接printf打印出来就是乱码。比较省事的处理是检查响应头里Content-Type字段的charsetxxx拿到后做一次转码。MinGW环境下可以用本地的MultiByteToWideChar和WideCharToMultiByte这对Windows API虽然参数多但不需要任何第三方库就能完成UTF-8和GBK之间的互转。4. 编译好的Windows版怎么用运行方式、参数说明与自编译流程4.1 命令行设计与输出约定标题里附带的编译好版本我习惯把它设计成纯命令行工具这样不管你是直接敲命令还是从其他脚本里用system()调用都非常顺手。程序支持的参数保持克制三个就够用--url指定目标地址--output指定保存到哪个文件--timeout设置超时秒数。不指定--output时正文直接打到标准输出方便配合管道继续处理。crawler.exe --url http://example.com/index.html --output page.html --timeout 15输出到文件时程序只保存提取后的HTML正文不包含响应头。这个设计是有意的——响应头里的Cookie、Server信息对你的分析没有帮助保存正文还能让文件更小。如果你确实需要响应头做调试加一个--show-header开关就能把响应头也打出来。4.2 一次完整运行的屏幕实录与结果解读假设你要抓一个新闻列表页命令和执行结果大致是这样的C:\ crawler.exe --url http://example.com/news --output news.html [INFO] 解析URL成功: 主机example.com, 端口80, 路径/news [INFO] 连接服务器成功, 延迟23ms [INFO] 发送GET请求完成 [INFO] 收到响应头: HTTP/1.1 200 OK Content-Type: text/html; charsetUTF-8 Content-Length: 138492 [INFO] 响应体提取完成: 138492 字节 [INFO] 已保存到 news.html你要重点看的不是前面的[INFO]而是两个地方一是连接延迟如果超过1000ms就要考虑是网络问题还是目标服务器限流二是正文的字节数和网页实际大小是否一致如果明显偏小多半是重定向没处理或者服务器按gzip压缩了内容。这个程序暂时不解压gzip遇到Content-Encoding: gzip的情况会把压缩后的乱码存下来识别方法就是保存的文件不是以html开头。这个问题在避坑章节会细说。4.3 从源码到exeMinGW编译与静态链接细节如果你拿到的是源码包而不是exe或者你想自己重新编译一版用MinGW的gcc一条命令就能完成。核心命令如下gcc -O2 -o crawler.exe crawler.c -lws2_32 -lwinmm-lws2_32链接Windows套接字库-lwinmm用于提供时间相关函数。编译完成后用strip crawler.exe把符号表去掉体积可以从几十KB缩到十几KB。这里有一个细节需要注意如果你用了#pragma comment(lib, ws2_32.lib)它在MSVC下有效但gcc会忽略它所以命令行里的-lws2_32不能少。分发exe时有一个关于运行库的关键问题——MinGW默认动态链接libgcc_s和libwinpthread这两个DLL。如果目标机器上没装这些DLLexe就会报“找不到入口点”。解决办法是给编译器加-static参数把所有运行库静态链接进exe里gcc -O2 -static -o crawler.exe crawler.c -lws2_32 -lwinmm加了这个参数后exe的体积会变大一些但换来的是在任何Windows机器上都直接能跑这个买卖很划算。我自己的习惯是始终-static输出因为“编译好的Windows版”这个词的关键不在编译而在“Windows版”三个字代表的可移植性。5. 避坑C语言爬虫的5个血泪经验与排查清单5.1 抓下来的HTML全是乱码现象保存的文件打开后是“首页”之类的怪字符但浏览器打开正常。原因服务器返回的是UTF-8编码而Windows记事本默认按ANSI(GBK)解码或者反过来服务器返回GBK你的程序却按UTF-8处理了。这个问题的根子在于程序没有检测响应头里的charsetxxx而是盲目地把字节流写进文件。解决先看Content-Type里的charset标识然后按标识转码。MinGW下转换UTF-8到GBK的常用写法是先用MultiByteToWideChar拿到宽字符长度再分配内存调WideCharToMultiByte两步走。如果你懒得写转码函数至少要在输出到stdout时调一下SetConsoleOutputCP(CP_UTF8)这样终端显示不会崩。5.2 程序连接成功但一直收不到数据现象程序打印了connected之后就没有下文了等十几秒后超时退出。原因第一发送的请求头里Host和实际连接的IP不匹配服务器直接挂起第二没有设置接收超时recv阻塞在等待状态第三服务器需要HTTP/1.1才会返回响应而你用了HTTP/1.0且没有完善的Host头。解决用Wireshark抓包看请求是否发出、服务器是否回了RST包。如果抓包显示请求发出后服务器始终不响应加一个Connection: close头再试同时务必设置SO_RCVTIMEO为5到10秒因为某些服务器确实会在响应完成后保持连接不关闭没有超时你就永远等不到返回0。5.3 文件保存了一半就结束了现象保存的HTML只有几KB但浏览器里的完整页面应该是几十KB。原因这是最简单的翻车现场——只调用了一次recv就把数据当完整响应写文件了。TCP是字节流一次recv能收到的数据量由内核缓冲和网络包分片共同决定完全不受sizeof(buf)控制。解决严格使用循环读直到recv返回值小于等于0。每次收到数据先追加到内存缓冲区检查是否已包含\r\n\r\n如果没有继续收。当所有数据收完后再一次性切分响应头和正文。我还会加一个保护逻辑如果缓冲区大小超过上限比如20MB强制退出并提示“响应体超过大小限制”防止某些恶意端点把程序内存拖垮。5.4 抓到的是压缩乱码不是HTML现象保存的文件以\x1f\x8b开头或全是不可读符号。原因服务器开启了gzip压缩响应头里有Content-Encoding: gzip你的程序没有解压就直接存储了。浏览器能正常看是因为它自动解压而裸socket不会替你干这件事。解决两条路任选。简单路——在请求头里加Accept-Encoding: identity告诉服务器别压缩很多站点会忽略这个建议但不少CDN会尊重它稳健路——收到响应头后检查Content-Encoding字段如果值是gzip就用zlib库或者Windows自带的RtlDecompressBuffer做解压。如果不想引入新依赖先走第一条路够用了。5.5 exe被杀毒软件当成木马删掉现象编译好的exe复制到目标机器Windows Defender直接隔离或报警。原因一个无签名的、来自网络的exe在Windows看来天然可疑如果程序里有连接网络、下载数据的动作触发启发式检测非常正常尤其当你的代码用了一些指针运算和内存拷贝时特征更容易“撞车”。解决最可靠的办法是在目标机器上自己从源码编译编译器生成的文件存活率高得多。另一个办法是给exe加数字签名但自签名的可信度有限如果你只是临时用可以先在杀毒软件里加白名单这个属于应急做法别到处传。我自己在给别的环境部署这类工具时一律发源码加编译脚本让现场自己编译一次编译出来的exe在杀毒软件面前基本不会翻车。6. 进阶技巧把一次性脚本变成能重复使用的抓取工具抓取本身不难难的是稳定地重复运行。我会给这个爬虫加一个循环模式传入一个URL模板和一个数字范围程序自动拼接URL并依次抓取每次抓完间隔1到3秒随机暂停。这个间隔是爬虫的德性底线也是对服务器最基本的礼貌——没有间隔的连续请求平时就容易被限流高峰期直接给你封IP。再有用一点的做法是把正文里的链接抽取出来。不需要引入正则库自己写一个简单的状态机扫描器从href开始读直到遇到为止把读到的相对路径拼接成绝对URL打印出来即可。用这个功能配合前面的循环抓取就能顺着一个目录页把整站静态页面逐一拉下来。我的习惯是给抓下来的每个文件自动命名目标URL的路径去掉斜杠加上时间戳后缀避免重复抓取时覆盖文件。同时把每次HTTP状态码记录到一行日志里方便事后统计哪些页面404了。这个细节看起不起眼但真正维护抓取任务时它比抓取代码本身更省心——日志里能直接告诉你哪里断了不用从头排查。这套思路稳定运行了很长时间希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

Python示例拆解:从推导式到装饰器,把代码变成真能力

Python示例拆解:从推导式到装饰器,把代码变成真能力

很多人学Python,最后都卡在同一个地方:语法都认识,代码不会写。市面上的教程从变量讲到函数、从列表讲到字典,每章都看懂了,合上书打开编辑器,面对一个空白的.py文件还是不知道第一行该敲什么。我早年带新人…

📅 2026/10/13 17:36:44
多任务学习实战指南:任务设计、参数架构与损失平衡

多任务学习实战指南:任务设计、参数架构与损失平衡

1. 多任务学习不是“一鱼两吃”,而是让模型学会举一反三多任务学习(Multi-Task Learning,MTL)这个词最近在技术社区里频繁出现,但很多人一听到就下意识觉得:“哦,就是让一个模型同时干几件事呗&…

📅 2026/10/13 17:36:44
ponytail浏览器脚本插件实战:从脚本注入到数据抓取与页面改造

ponytail浏览器脚本插件实战:从脚本注入到数据抓取与页面改造

1. 从“ponytail”这个词说起:它到底是什么第一次看到“ponytail”这个词,大多数人脑子里浮现的是发型——马尾辫。但在插件圈子里,ponytail 指的是一款浏览器端的轻量级脚本管理与页面增强工具。它的定位很明确:不追求大而全&…

📅 2026/10/13 17:36:44
MORE NEWS

更多资讯

📰

Unity TMP中文字体Asset生成指南:字符集搭建与参数配置

之前有次接手一个Unity项目,一打开UI界面,中文全部变成白色方框。排查了半天,不是贴图丢失,是TextMeshPro没有中文字形。TMP默认字体文件里只有拉丁字符,项目只要显示简体中文,就必须先准备一个包含中文字形…

📰

DICOM转NII:元数据语义重建与坐标系校准实战指南

1. 为什么DICOM转NII不是“点一下就完事”的操作在某高校医学影像实验室带学生做课题时,我见过太多次这样的场景:刚接触影像处理的A同学兴冲冲跑来问:“老师,网上说用dcm2niix就能把DICOM转成NII,我拖了文件夹进去&…

📰

租房数据可视化系统:基于Flask与Layui的Python全栈开发实践

今年这个选题方向确实很值得好好说一说。计算机相关专业的毕业设计,年年都逃不开“要做一个能跑、能讲、能演示的系统”这场大考。租房数据可视化分析系统,用 Python 做全栈开发,后端走 Flask 框架、前端用 Layui 框架,再把房源、…

📰

精简中文字符集,解决Unity TMP缺字与生成慢

做 UI 的应该都有过这种经历:界面写好了,文案也填进去了,一进 Play Mode,TextMeshPro 文本里密密麻麻全是方块和空白。问题不出在文案,而是字体资源里根本没有这些字形。Unity TextMeshPro 的 SDF 渲染方案&#xff0c…

📰

从O(n²)到O(n log n):Barnes-Hut算法与N体模拟器实现指南

简介:一份C实现的Barnes-Hut模拟器源码包,面向需要掌握N体问题高效计算方法的开发者与天体物理爱好者。项目以八叉树结构递归划分三维空间,通过远近法则用节点质心近似远处引力,避免逐对力计算,在大规模粒子系统下显著…

📰

Simulink DEE学习课件:微分方程编辑器与连续系统建模实战指南

简介:面向 Simulink DEE 初学者与动态系统建模人员,这份入门课件着力解决微分方程建模与仿真的上手难题,即使非程序员也能借助图形界面快速构建复杂系统。压缩包整体仅 1.42MB,共 3 个文件:PDF 版详细教程深入讲解 DEE…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬