商务服务
python多线程爬虫 爬取多个网页_网络爬虫:使用多线程爬取网页链接
2025-01-03 13:47

前言

经过前面两篇文章,你想大家应该已经知道网络爬虫是怎么一回事了。这篇文章会在之前做过的事情上做一些改进,以及说明之前的做法的不足之处。

思路分析

1.逻辑结构图

上图中展示的就是我们网络爬虫中的整个逻辑思路(调用Python解析URL,这里只作了简略的展示)。

2.思路说明

首先,我们来把之前思路梳理一下。之前我们采用的两个队列Queue来保存已经访问过和待访问的链接列表,并采用广度优先搜索进行递归访问这些待访问的链接地址。而且这里使用的是单线程操作。在对数据库的操作中,我们添加了一个辅助字段cipher_address来进行“唯一”性保证,因为我们担心MySQL在对过长的url链接操作时会有一些不尽如人意。

我不知道上面这一段能否让你对之前我们处理Spider的做法有一个大概的了解,如果你还没有太明白这是怎么一回事。你可以访问《网络爬虫初步:从访问网页到数据解析》和《网络爬虫初步:从一个入口链接开始不断抓取页面中的网址并入库》这两篇文章进行了解。

下面我就来说明一下,之前的做法存在的问题

1.单线程:采用单线程的做法,可以说相当不科学,尤其是对付这样一个大数据的问题。所以,我们需要采用多线程来处理问题,这里会用到多线程中的线程池。

2.数据存储方式:如果我们采用内存去保存数据,这样会有一个问题,因为数据量非常大,所以程序在运行的过种中必然会内存溢出。而事实也正是如此

3.Url去重的方式:如果我们对Url进行MD5或是SHA1进行加密的方式进行哈希的话,这样会有一个效率的隐患。不过的确这个问题并不那么复杂。对效率的影响也很小。不过,还好Java自身就已经对String型的数据有哈希的函数可以直接调用:hashCode()

代码及说明

linkSpider.java

public class linkSpider {

private SpiderQueue queue = null;

public void ErgodicNetworklink(String startAddress) {

if (startAddress == null) {

return;

}

SpiderBLL.insertEntry2DB(startAddress);

List modelList = new ArrayList();

queue = SpiderBLL.getAddressQueue(startAddress, 0);

if (queue.isQueueEmpty()) {

System.out.println("Your address cannot get more address.");

return;

}

ThreadPoolExecutor threadPool = getThreadPool();

int index = 0;

boolean breakFlag = false;

while (!breakFlag) {

// 待访问队列为空时的处理

if (queue.isQueueEmpty()) {

System.out.println("queue is null...");

modelList = DBBLL.getUnvisitedInfoModels(queue.MAX_SIZE);

if (modelList == null || modelList.size() == 0) {

breakFlag = true;

} else {

for (WebInfoModel webInfoModel : modelList) {

queue.offer(webInfoModel);

DBBLL.updateUnvisited(webInfoModel);

}

}

}

WebInfoModel model = queue.poll();

if (model == null) {

continue;

}

// 判断此网站是否已经访问过

if (DBBLL.isWebInfoModelExist(model)) {

// 如果已经被访问,进入下一次循环

System.out.println("已存在此网站(" + model.getName() + ")");

continue;

}

poolQueueFull(threadPool);

System.out.println("LEVEL: [" + model.getLevel() + "] NAME: " + model.getName());

SpiderRunner runner = new SpiderRunner(model.getAddress(), model.getLevel(), index++);

threadPool.execute(runner);

SystemBLL.cleanSystem(index);

// 对已访问的address进行入库

DBBLL.insert(model);

}

threadPool.shutdown();

}

private ThreadPoolExecutor getThreadPool() {

final int MAXIMUM_POOL_SIZE = 520;

final int CORE_POOL_SIZE = 500;

return new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, 3, TimeUnit.SECONDS, new ArrayBlockingQueue(MAXIMUM_POOL_SIZE), new ThreadPoolExecutor.DiscardOldestPolicy());

}

private void poolQueueFull(ThreadPoolExecutor threadPool) {

while (getQueueSize(threadPool.getQueue()) >= threadPool.getMaximumPoolSize()) {

System.out.println("线程池队列已满,等3秒再添加任务");

try {

Thread.sleep(2000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

private synchronized int getQueueSize(Queue queue) {

return queue.size();

}

class SpiderRunner implements Runnable {

private String address;

private SpiderQueue auxiliaryQueue; // 记录访问某一个网页中解析出的网址

private int index;

private int parentLevel;

public SpiderRunner(String address, int parentLevel, int index) {

this.index = index;

this.address = address;

this.parentLevel = parentLevel;

}

public void run() {

auxiliaryQueue = SpiderBLL.getAddressQueue(address, parentLevel);

System.out.println("[" + index + "]: " + address);

DBBLL.insert2Unvisited(auxiliaryQueue, index);

auxiliaryQueue = null;

}

}

}在上面的ErgodicNetworklink方法代码中,大家可以看到我们已经把使用Queue保存数据的方式改为使用数据库存储。这样做的好处就是我们不用再为OOM而烦恼了。而且,上面的代码也使用了线程池。使用多线程来执行在调用Python获得链接列表的操作。

而对于哈希Url的做法,可以参考如下关键代码

public static void insert2Unvisited(WebInfoModel model) {

if (model == null) {

return;

}

String sql = "INSERT INTO unvisited_site(name, address, hash_address, date, visited, level) VALUES('" + model.getName() + "', '" + model.getAddress() + "', " + model.getAddress().hashCode() + ", " + System.currentTimeMillis() + ", 0, " + model.getLevel() + ");";

DBServer db = null;

try {

db = new DBServer();

db.insert(sql);

db.close();

} catch (Exception e) {

System.out.println("your sql is: " + sql);

e.printStackTrace();

} finally {

db.close();

}

}

PythonUtils.java

这个类是与Python进行交互操作的类。代码如下

public class PythonUtils {

// Python文件的所在路径

private static final String PY_PATH = "/root/python/WeblinkSpider/html_parser.py";

private static String[] getShellArgs(String address) {

String[] shellParas = new String[3];

shellParas[0] = "python";

shellParas[1] = PY_PATH;

shellParas[2] = address.replace(""", "\"");

return shellParas;

}

private static WebInfoModel parserWebInfoModel(String info, int parentLevel) {

if (BEEStringTools.isEmptyString(info)) {

return null;

}

String[] infos = info.split("\$#\$");

if (infos.length != 2) {

return null;

}

if (BEEStringTools.isEmptyString(infos[0].trim())) {

return null;

}

if (BEEStringTools.isEmptyString(infos[1].trim()) || infos[1].trim().equals("http://") || infos[1].trim().equals("https://")) {

return null;

}

WebInfoModel model = new WebInfoModel();

model.setName(infos[0].trim());

model.setAddress(infos[1]);

model.setLevel(parentLevel + 1);

return model;

}

private static SpiderQueue getAddressQueueByPython(String[] shellParas, int parentLevel) {

if (shellParas == null) {

return null;

}

Runtime r = Runtime.getRuntime();

SpiderQueue queue = null;

try {

Process p = r.exec(shellParas);

BufferedReader bfr = new BufferedReader(new InputStreamReader(p.getInputStream()));

queue = new SpiderQueue();

String line = "";

WebInfoModel model = null;

while((line = bfr.readLine()) != null) {

// System.out.println("----------> from python: " + line);

if (BEEStringTools.isEmptyString(line.trim())) {

continue;

}

if (HttpBLL.isErrorStateCode(line)) {

break;

}

model = parserWebInfoModel(line, parentLevel);

if (model == null) {

continue;

}

queue.offer(model);

}

model = null;

line = null;

} catch (IOException e) {

e.printStackTrace();

} finally {

r = null;

}

return queue;

}

public static SpiderQueue getAddressQueueByPython(String address, int parentLevel) {

return getAddressQueueByPython(getShellArgs(address), parentLevel);

}

}

遇到的问题

1.请使用Python2.7

因为Python2.6中HTMLParser还是有一些缺陷的,例如下图中展示的。不过在Python2.7中,这个问题就不再是问题了。

2.数据库崩溃了

数据库崩溃的原因可能是待访问的数据表中的数据过大引起的。

3.对数据库的同步操作

上面的做法是对数据库操作进行同步时出现的问题,如果不进行同步,我们会得到数据库连接数超过最大连接数的异常信息。对于这个问题有望在下篇文章中进行解决。

    以上就是本篇文章【python多线程爬虫 爬取多个网页_网络爬虫:使用多线程爬取网页链接】的全部内容了,欢迎阅览 ! 文章地址:https://sicmodule.kub2b.com/news/15265.html
     栏目首页      相关文章      动态      同类文章      热门文章      网站地图      返回首页 企库往资讯移动站 https://sicmodule.kub2b.com/mobile/ , 查看更多   
最新文章
手机贴膜硬核科普,一分钟搞懂8种手机膜的区别手机钢化膜「手机贴膜硬核科普,一分钟搞懂8种手机膜的区别」
创作立场声明:文中列举商品仅为示范作用,与品牌无关。说起手机贴膜,想必大家并不陌生,很多人拿到手机后的第一件事,就是贴膜
你以为它死了,其实它复活了,诺基亚手机回归带来十个疑问高颜值手机「你以为它死了,其实它复活了,诺基亚手机回归带来十个疑问」
  2008年1月16日,德国波鸿,在一次员工示威期间,一位诺基亚公司的女员工落泪。你以为它死了,其实它复活了,是的,说的就是
华为折叠手机2023新款价格 华为最新款手机折叠华为新款手机「华为折叠手机2023新款价格 华为最新款手机折叠」
折叠手机是智能手机的一种造型,柔性AMOLED屏幕是折叠手机的突破关键。寰宇舷窗,探索未来独创寰宇舷窗设计,以探索之姿洞见未⁠
139手机邮箱注册(139手机号邮箱注册)
  关于《139手机邮箱注册》的文章  在当今信息化社会,电子邮件已成为人们日常生活和工作中不可或缺的一部分。而手机邮箱因
信息门户手机信息「信息门户」
我校信息门户于2019年1月上线,与南京大学APP互为移动端服务补充,为师生提供在线服务、消息提醒、推文宣传等服务功能。 微信搜
手机能一直开着录音吗 手机一直开着录音行吗【详解】手机录音「手机能一直开着录音吗 手机一直开着录音行吗【详解】」
  能一直开着录音,但是要保证电量和储存空间的充足。一旦录音的储存空间被占满,录音就会停止,保证电量充足,可以边充边录音
张蔷属于昨天,更属于“明天”(音乐节)v i v o 手机「张蔷属于昨天,更属于“明天”(音乐节)」
张蔷,中国内地流行音乐代表人物,传奇天才女歌手,80年代中国流行文化偶像符号,21世纪迪斯科回潮的新女皇。 从小深受从事音乐
2k14手机(2k14手机版中文版下载)
  《2K14手机》:超越视觉的极致体验  在当今科技飞速发展的时代,手机已经成为了我们生活中不可或缺的一部分。而《2K14手机
适合情侣玩的手机游戏前五名 有适合两个人玩的游戏吗情侣手机「适合情侣玩的手机游戏前五名 有适合两个人玩的游戏吗」
游戏还是两个人一起玩有意思,特别是情侣之间,不但能娱乐,还能增进俩人之间的亲密感情。还有异地恋的情侣们,每天只能依靠煲电
创新之城,非凡园区!星海红领巾访园区展示中心v i v o 手机「创新之城,非凡园区!星海红领巾访园区展示中心」
创新之城 非凡园区红领巾寻访苏州工业园区展示中心 这里的街道宽敞整洁,很少见到密如蛛网的电线和凌乱的街边小店; 这里的马路