Getting a list of nearby WiFi access points in Python on macOS

Book Lailert
Mac O’Clock
Published in
4 min readApr 15, 2020

--

Using Python libraries to access OS functions on macOS can be a bit of a pain as most of the libraries are written for a more popular (and probably less locked down) OS like Windows and Linux. As WiFi scanning (and controlling the WiFi card in general) is an OS feature (for most modern Macs anyway) it can be quite difficult to work within Python. During my research for this project, I did run into a Python library called Scapy, but I haven’t gotten it to work on macOS quite yet.

macOS does allow you to search for nearby WiFi access points using the built-in airport command in Terminal. However, you won’t be able to access this command right out of the box. In order to be able to access it, you must run this command in Terminal:

sudo ln -s /System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport /usr/local/bin/airport

This commands creates a symbolic link to the airport command allowing it to be called in the terminal without going into the directory itself.

For older versions of macOS (High Sierra and earlier)

sudo ln -s /System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport /usr/sbin/airport

Once that is done, try running

airport -h

and a help page of the airport command should come up. If it does you should be good to continue.

The airport command allows you to do a few different things, what we’re interested in, however, is this command

airport -s

Which scans and list out WiFi access points around you, along with the SSID, mac address, RSSI, channels etc. Another command that may be useful to you is

airport -I

which will show the information about the access point that your Mac is currently connected to.

Note: This command only works when your Mac has WiFi turned on and your WiFi card is not on monitor mode.

Now that we have familiarised ourselves with the airport command. We can start working on our Python code.

In order to send a terminal command to the OS, we will be using the Python built-in library subprocess. Subprocess allows you to run terminal commands within Python and capture its output.

Before sending the command and receiving the input, we must first create a variable containing the command that is going to be sent to the system.

import subprocessscan_cmd = subprocess.Popen(['airport', '-s'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

then you send the command to the OS and wait for a response

scan_out, scan_err = scan_cmd.communicate()

scan_out will contain the output of the command
scan_err will contain the error produced by the command (will be None if no error is produced)

Now, if we go ahead and run the command and print out scan_out we will see something like this:

SSID BSSID             RSSI CHANNEL HT CC SECURITY (auth/unicast/group)
my_wifi 12:34:56:78:90:12 -40 5,-1 Y US WPA(PSK/TKIP,AES/TKIP) WPA2(PSK/TKIP,AES/TKIP)
my_wifi_2 23:45:67:89:01"23 -56 13 Y TH NONE

which is not very useful to us, so in order to extract data from this string we will need to split this data into lists. Each line is separated using the special character \\n so we will first separate the data using this method

scan_out_lines = str(scan_out).split("\\n")

scan_out is returned as bytes encoded string, so we must convert it to string first

If you print this out, you should now see a list where the first element contains the column headers and the rest are a string containing the information about each access point. You may also notice that the last element is just a random mostly empty string. In order to continue, we must then exclude the first and the last element of the list. To do so, replace the line above with

scan_out_lines = str(scan_out).split("\\n")[1:-1]

this slices the list leaving every other element but the first and the last.

Now that we’ve ended up with a string for each access point. What we will do next is loop through the whole list and separate each line by spaces.

scan_out_data = []
for each_line in scan_out_lines:
scan_out_data.append(each_line.split(" "))

If you now print out scan_out_data you should see a 2D list. However, the inner list will contain a lot of empty spaces, to remove these, replace the code segment above with:

scan_out_data = []
for each_line in scan_out_lines:
split_line = [e for e in each_line.split(" ") if e != ""]
scan_out_data.append(split_line)

Now scan_out data should be a 2D list containing all the information for each router.

Alternatively, if you’d rather work with dictionaries you can do this instead.

scan_out_data = {}
for each_line in scan_out_lines:
split_line = [e for e in each_line.split(" ") if e != ""]
line_data = {"SSID": split_line[0], "RSSI": int(split_line[2]), "channel": split_line[3], "HT": (split_line[4] == "Y"), "CC": split_line[5], "security": split_line[6]}
scan_out_data[split_line[1]] = line_data

You can then query the data for each access points by calling the dictionary, providing the mac address as the key.

Here’s the complete piece of code for this function

import subprocess
#returns list
def get_aps():
scan_cmd = subprocess.Popen(['airport', '-s'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
scan_out, scan_err = scan_cmd.communicate()
scan_out_data = []
scan_out_lines = str(scan_out).split("\\n")[1:-1]
for each_line in scan_out_lines:
split_line = [e for e in each_line.split(" ") if e != ""]
scan_out_data.append(split_line)
return scan_out_data

Or

import subprocess
#returns dictionary
def get_aps():
scan_cmd = subprocess.Popen(['airport', '-s'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
scan_out, scan_err = scan_cmd.communicate()
scan_out_data = {}
scan_out_lines = str(scan_out).split("\\n")[1:-1]
for each_line in scan_out_lines:
split_line = [e for e in each_line.split(" ") if e != ""]
line_data = {"SSID": split_line[0], "RSSI": int(split_line[2]), "channel": split_line[3], "HT": (split_line[4] == "Y"), "CC": split_line[5], "security": split_line[6]}
scan_out_data[split_line[1]] = line_data
return scan_out_data

--

--

Book Lailert
Mac O’Clock

A-Level student in Bangkok, Thailand. Interested in Computer Science and Programming.