├── tests ├── __init__.py ├── test_proxies.py ├── test_filters.py └── test_ais.py ├── poetry.toml ├── .gitattributes ├── requirements.txt ├── aisexplorer ├── __init__.py ├── Utils │ └── Utility.py ├── Exceptions.py ├── Proxy.py ├── Filters.py └── AIS.py ├── .github └── workflows │ ├── publish_to_pypi.yml │ └── python-package.yml ├── pyproject.toml ├── .gitignore ├── README.md └── examples └── Short_Example.ipynb /tests/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /poetry.toml: -------------------------------------------------------------------------------- 1 | [virtualenvs] 2 | in-project = true 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reyemb/AISExplorer/HEAD/requirements.txt -------------------------------------------------------------------------------- /aisexplorer/__init__.py: -------------------------------------------------------------------------------- 1 | from aisexplorer import AIS, Exceptions 2 | from aisexplorer import Proxy 3 | 4 | __version__ = "0.2.1" 5 | -------------------------------------------------------------------------------- /.github/workflows/publish_to_pypi.yml: -------------------------------------------------------------------------------- 1 | name: Publish to PYPI 2 | on: 3 | push: 4 | branches: ["main"] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - name: Build and publish to pypi 11 | uses: JRubics/poetry-publish@v1.16 12 | with: 13 | python_version: "3.10.6" 14 | pypi_token: ${{ secrets.PYPI_TOKEN }} 15 | -------------------------------------------------------------------------------- /tests/test_proxies.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from aisexplorer.Proxy import FreeProxy 4 | 5 | 6 | class TestProxy(unittest.TestCase): 7 | def test_proxy(self): 8 | proxy = FreeProxy() 9 | self.assertIsInstance(proxy, FreeProxy) 10 | 11 | def test_proxy_list(self): 12 | proxy = FreeProxy() 13 | proxy.fetch_proxy_list() 14 | self.assertTrue(not proxy.proxies.empty) 15 | 16 | def test_proxy_list_len(self): 17 | proxy = FreeProxy() 18 | proxy.fetch_proxy_list() 19 | self.assertTrue(len(proxy.proxies) > 0) 20 | -------------------------------------------------------------------------------- /tests/test_filters.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from aisexplorer.Filters import Filters 4 | 5 | 6 | class TestFilters(unittest.TestCase): 7 | def test_filter_1(self): 8 | self.assertEqual( 9 | Filters(mmsi=3812, dwt=[1, 2]).to_query(), 10 | "&mmsi|eq|mmsi=3812&dwt_between|range|dwt_between=1,2", 11 | ) 12 | 13 | def test_filter_2(self): 14 | self.assertEqual( 15 | Filters(latest_report=[360, 525600]).to_query(), 16 | "&time_of_latest_position_between|gte|time_of_latest_position_between=360,525600", 17 | ) 18 | 19 | def test_filter_3(self): 20 | self.assertEqual( 21 | Filters(lon=[20, 30]).to_query(ignore_filter="vessel_name"), 22 | "&lon_of_latest_position_between|range|lon_of_latest_position_between=20,30", 23 | ) 24 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "AISExplorer" 3 | version = "0.2.2" 4 | description = "Wrapper to fetch data from marinetraffic" 5 | readme = "README.md" 6 | homepage = "https://github.com/reyemb/AISExplorer" 7 | repository = "https://github.com/reyemb/AISExplorer" 8 | keywords = ["AIS", "Vessel", "Shiptracking","Shiplocation","Location","Proxy", "marinetraffic"] 9 | authors = ["reyemb "] 10 | 11 | [tool.poetry.dependencies] 12 | python = ">=3.9" 13 | pandas = ">=1.3.4" 14 | requests = ">=2.26.0" 15 | lxml = ">=4.6.4" 16 | tenacity = ">=8.0.1" 17 | numpy = "^1.26.2" 18 | 19 | [tool.poetry.dev-dependencies] 20 | readme-renderer = ">=30.0" 21 | jupyter = ">=1.0.0" 22 | black = ">=22.12.0" 23 | python-dotenv = ">=0.21.0" 24 | 25 | [build-system] 26 | requires = ["poetry-core>=1.0.0"] 27 | build-backend = "poetry.core.masonry.api" 28 | -------------------------------------------------------------------------------- /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests with a variety of Python versions 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python 3 | 4 | name: Python package 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | python-version: ["3.8", "3.9", "3.10"] 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | - name: Set up Python ${{ matrix.python-version }} 22 | uses: actions/setup-python@v3 23 | with: 24 | python-version: ${{ matrix.python-version }} 25 | - name: Install dependencies 26 | run: | 27 | python -m pip install --upgrade pip 28 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 29 | - name: Test with unittest 30 | run: | 31 | python -m unittest 32 | -------------------------------------------------------------------------------- /tests/test_ais.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import pandas as pd 3 | 4 | from aisexplorer.AIS import AIS 5 | 6 | 7 | class TestAis(unittest.TestCase): 8 | def test_return_type_df(self): 9 | self.assertIsInstance( 10 | AIS(verbose=False, return_df=True).get_area_data("WMED"), pd.DataFrame 11 | ) 12 | 13 | def test_return_type_list(self): 14 | self.assertIsInstance( 15 | AIS(verbose=False, return_df=False).get_area_data("WMED"), list 16 | ) 17 | 18 | def test_fetch_by_url(self): 19 | self.assertTrue( 20 | len( 21 | AIS(verbose=True, return_df=True).get_data_by_url( 22 | "https://www.marinetraffic.com/en/data/?asset_type=vessels&columns=time_of_latest_position:desc,flag,shipname,photo,recognized_next_port,reported_eta,reported_destination,current_port,imo,ship_type,show_on_live_map,area,lat_of_latest_position,lon_of_latest_position,speed,length,width&area_in|in|West%20Mediterranean,East%20Mediterranean|area_in=WMED,EMED&time_of_latest_position_between|gte|time_of_latest_position_between=60,525600https://www.marinetraffic.com/en/data/?asset_type=vessels&columns=time_of_latest_position:desc,flag,shipname,photo,recognized_next_port,reported_eta,reported_destination,current_port,imo,ship_type,show_on_live_map,area,lat_of_latest_position,lon_of_latest_position,speed,length,width&area_in|in|West%20Mediterranean,East%20Mediterranean|area_in=WMED,EMED&time_of_latest_position_between|gte|time_of_latest_position_between=60,525600" 23 | ) 24 | ) 25 | > 100 26 | ) 27 | 28 | def test_fetch_by_area(self): 29 | self.assertTrue( 30 | len(AIS(verbose=False, return_df=False).get_area_data("WMED")) > 100 31 | ) 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | .idea 28 | aisexplorer/__pycache__ 29 | 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | target/ 75 | 76 | # Jupyter Notebook 77 | .ipynb_checkpoints 78 | 79 | # IPython 80 | profile_default/ 81 | ipython_config.py 82 | 83 | # pyenv 84 | .python-version 85 | 86 | # celery beat schedule file 87 | celerybeat-schedule 88 | 89 | # SageMath parsed files 90 | *.sage.py 91 | 92 | # Environments 93 | .env 94 | .venv 95 | env/ 96 | venv/ 97 | ENV/ 98 | env.bak/ 99 | venv.bak/ 100 | 101 | # Spyder project settings 102 | .spyderproject 103 | .spyproject 104 | 105 | # Rope project settings 106 | .ropeproject 107 | 108 | # mkdocs documentation 109 | /site 110 | 111 | # mypy 112 | .mypy_cache/ 113 | .dmypy.json 114 | dmypy.json 115 | 116 | # Pyre type checker 117 | .pyre/ 118 | 119 | #Poetry Stuff 120 | poetry.lock 121 | pyproject.toml 122 | 123 | #Notebok Stuff 124 | .ipynb_checkpoints -------------------------------------------------------------------------------- /aisexplorer/Utils/Utility.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | 3 | # Define columns and their respective data types 4 | COLUMN_TYPES = { 5 | "int": [ 6 | "SHIP_ID", 7 | "IMO", 8 | "MMSI", 9 | "TYPE_COLOR", 10 | "TIMEZONE", 11 | "COUNT_PHOTOS", 12 | "NEXT_PORT_ID", 13 | "COURSE", 14 | "YOB", 15 | "DWT", 16 | "ETA_OFFSET", 17 | "DISTANCE_TO_GO", 18 | "PORT_ID", 19 | "INLAND_ENI", 20 | ], 21 | "float": ["LON", "LAT", "SPEED", "DRAUGHT", "LENGTH", "WIDTH"], 22 | "bool": ["CTA_ROUTE_FORECAST"], 23 | "str": [ 24 | "CALLSIGN", 25 | "SHIPNAME", 26 | "CODE2", 27 | "COUNTRY", 28 | "NEXT_PORT_NAME", 29 | "NEXT_PORT_COUNTRY", 30 | "DESTINATION", 31 | "TYPE_SUMMARY", 32 | "STATUS_NAME", 33 | "CURRENT_PORT_UNLOCODE", 34 | "CURRENT_PORT_COUNTRY", 35 | "AREA_CODE", 36 | "STATUS", 37 | "AREA_NAME", 38 | "CURRENT_PORT", 39 | "COLLECTION_NAME", 40 | ], 41 | "unix": ["LAST_POS"], 42 | "unix_masked": ["ETA"], 43 | "timestamp": ["ETA_UPDATED"], 44 | } 45 | 46 | 47 | def set_types_df(df): 48 | """ 49 | Set the data types for a DataFrame's columns based on predefined mappings. 50 | 51 | Args: 52 | df (pd.DataFrame): The DataFrame whose data types are to be set. 53 | 54 | Returns: 55 | pd.DataFrame: The DataFrame with updated data types. 56 | """ 57 | df = df.copy() 58 | 59 | # Apply types for each group of columns 60 | for dtype, cols in COLUMN_TYPES.items(): 61 | if dtype == "int": 62 | df[cols] = df[cols].apply(pd.to_numeric, errors="coerce") 63 | elif dtype == "float": 64 | df[cols] = df[cols].apply(pd.to_numeric, errors="coerce") 65 | elif dtype == "bool": 66 | df[cols] = df[cols].astype(bool) 67 | elif dtype == "str": 68 | df[cols] = df[cols].astype(str) 69 | elif dtype in ["unix", "unix_masked"]: 70 | for col in cols: 71 | if dtype == "unix_masked": 72 | df[col] = df[col].apply(lambda x: x if x != "masked" else None) 73 | df[col] = pd.to_numeric(df[col], errors="coerce") 74 | df[col] = pd.to_datetime(df[col], unit="s", errors="coerce") 75 | elif dtype == "timestamp": 76 | df[cols] = df[cols].apply(pd.to_datetime, errors="coerce") 77 | 78 | return df 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AISExplorer 2 | 3 | ![PyPI](https://img.shields.io/pypi/v/AISExplorer) 4 | ![PyPI - Downloads](https://img.shields.io/pypi/dm/AISExplorer) 5 | 6 | AISExplorer is a tool for locating vessels or scraping vessel data in a specific area. However, due to recent updates, the use of free proxies, previously scraped from sources like [sslproxies](https://www.sslproxies.org/), has been discontinued due to errors such as 403 Forbidden responses. 7 | 8 | ## Next Steps 9 | 10 | - Explore additional sources for proxy lists. 11 | - Implement a method to customize the number of retries. 12 | - Introduce functionality to reset filters. 13 | 14 | ## Changelog 15 | 16 | ### 2023-11-10 17 | 18 | - Due to Captcha implementation, login functionality is broken. 19 | - Sending requests through proxies now leads to a 403 error; proxy support has been removed. 20 | 21 | ### 2023-1-21 22 | 23 | - Integrated login functions for additional features. 24 | 25 | ### 2021-12-10 26 | 27 | - Implemented fallback if a proxy fails. 28 | - Introduced the ability to retrieve data directly via URL. 29 | - Added checks for Cloudflare's filtering mechanisms. 30 | 31 | ### 2021-12-5 32 | 33 | - Early stages of filter implementation. 34 | - Retry options were added for resilience. 35 | - New exceptions were introduced for better error handling. 36 | 37 | ### 2021-11-27 38 | 39 | - Proxy support was added (discontinued as of 2023-11-10). 40 | 41 | ## Installation 42 | 43 | ``` cmd 44 | pip install aisexplorer 45 | ``` 46 | 47 | ## Usage 48 | 49 | ### Find vessel by MMIS 50 | Retrieve the current location of a vessel using its MMSI identifier. 51 | 52 | ```python 53 | from aisexplorer.AIS import AIS 54 | 55 | AIS().get_location(211281610) 56 | ``` 57 | 58 | ### Find vessels in Area 59 | Retrieve data for up to 500 vessels within a designated area. 60 | 61 | **maximum 500 vessels** 62 | 63 | ```python 64 | from aisexplorer.AIS import AIS 65 | 66 | AIS(return_df= True).get_area_data("EMED") 67 | ``` 68 | 69 | The output is limited to 500 rows. Area codes can be referenced from the MarineTraffic help section. 70 | [Areas](https://help.marinetraffic.com/hc/en-us/articles/214556408-Areas-of-the-World-How-does-MarineTraffic-segment-them-) can be found here 71 | 72 | ### Get Table via URL 73 | Directly access table data using a MarineTraffic URL. 74 | 75 | ```python 76 | from aisexplorer.AIS import AIS 77 | 78 | AIS(return_df = True).get_data_by_url("https://www.marinetraffic.com/en/data/?asset_type=vessels&columns=time_of_latest_position:desc,flag,shipname,photo,recognized_next_port,reported_eta,reported_destination,current_port,imo,ship_type,show_on_live_map,area,lat_of_latest_position,lon_of_latest_position,speed,length,width&area_in|in|West%20Mediterranean,East%20Mediterranean|area_in=WMED,EMED&time_of_latest_position_between|gte|time_of_latest_position_between=60,NaN") 79 | ``` 80 | 81 | ### Use Proxies 82 | 83 | Previously, AISExplorer allowed fetching data using proxies for anonymization. This feature is no longer supported due to compatibility issues with the data source. 84 | 85 | ### Get Data for user created fleets 86 | 87 | No longer available as it required user login, which is now deprecated due to captcha implementation. 88 | 89 | -------------------------------------------------------------------------------- /aisexplorer/Exceptions.py: -------------------------------------------------------------------------------- 1 | import difflib 2 | 3 | 4 | class MalformedFunctionError(Exception): 5 | """Exception raised for errors in the formation of a function.""" 6 | 7 | def __init__(self, message: str): 8 | """ 9 | Args: 10 | message: Explanation of the error. 11 | """ 12 | super().__init__(f"Function is malformed\n{message}") 13 | 14 | 15 | class NotSupportedParameterTypeError(MalformedFunctionError): 16 | """Exception raised for unsupported parameter types in a function.""" 17 | 18 | def __init__(self, parameter: str, supported_types: list, given_type: type): 19 | """ 20 | Args: 21 | parameter: The parameter that received the unsupported type. 22 | supported_types: A list of supported types for the parameter. 23 | given_type: The type of the argument that was provided. 24 | """ 25 | message = ( 26 | f"{parameter} does only support the following instances {', '.join(supported_types)} " 27 | f"but {given_type} has been given" 28 | ) 29 | super().__init__(message) 30 | 31 | 32 | class NotSupportedParameterError(MalformedFunctionError): 33 | """Exception raised for unsupported parameter values in a function.""" 34 | 35 | def __init__(self, parameter: str, supported_arguments: list, given_argument: str): 36 | """ 37 | Args: 38 | parameter: The parameter that received the unsupported value. 39 | supported_arguments: A list of supported arguments for the parameter. 40 | given_argument: The argument that was provided. 41 | """ 42 | suggestion = difflib.get_close_matches(given_argument, supported_arguments) 43 | message = ( 44 | f"{parameter} only accepts the following arguments {','.join(supported_arguments)}, " 45 | f"but {given_argument} was given. Did you mean {suggestion}?" 46 | ) 47 | super().__init__(message) 48 | 49 | 50 | class MalformedFilterError(Exception): 51 | """Exception raised for errors in the formation of a filter.""" 52 | 53 | def __init__(self, message: str): 54 | """ 55 | Args: 56 | message: Explanation of the error. 57 | """ 58 | super().__init__(f"Filter is malformed\n{message}") 59 | 60 | 61 | class NotSupportedKeyError(MalformedFilterError): 62 | """Exception raised for unsupported keys in a filter.""" 63 | 64 | def __init__(self, supported_keys: list, given_key: str): 65 | """ 66 | Args: 67 | supported_keys: A list of keys that are supported. 68 | given_key: The key that was used and is not supported. 69 | """ 70 | suggestion = difflib.get_close_matches(given_key, supported_keys) 71 | message = ( 72 | f"The following keys are accepted: {','.join(supported_keys)}, " 73 | f"but {given_key} was given. Did you mean {suggestion}?" 74 | ) 75 | super().__init__(message) 76 | 77 | 78 | class NotSupportedKeyTypeError(MalformedFilterError): 79 | """Exception raised for unsupported value types for a given key in a filter.""" 80 | 81 | def __init__(self, key: str, value, accept_types: list): 82 | """ 83 | Args: 84 | key: The key that was used in the filter. 85 | value: The value that was provided. 86 | accept_types: A list of accepted types for the value. 87 | """ 88 | message = ( 89 | f"For the {key} the following value types are accepted: {','.join(accept_types)}, " 90 | f"but {value} was given." 91 | ) 92 | super().__init__(message) 93 | 94 | 95 | class NotSupportedArgumentType(MalformedFilterError): 96 | """Exception raised for arguments of an unsupported form in a filter.""" 97 | 98 | def __init__(self, key: str, given_form: int, accepted_form: int): 99 | """ 100 | Args: 101 | key: The key that was used in the filter. 102 | given_form: The form/length of the arguments that was provided. 103 | accepted_form: The expected form/length of the arguments. 104 | """ 105 | message = f"For the Filter {key} the arguments should have a length of {accepted_form} but {given_form} was given." 106 | super().__init__(message) 107 | 108 | 109 | class NoResultsError(Exception): 110 | """Exception raised when no Results are given.""" 111 | 112 | def __init__(self, message): 113 | super().__init__(message) 114 | 115 | 116 | class CloudflareError(Exception): 117 | """Exception raised when Cloudflare detects unusual behavior.""" 118 | 119 | def __init__(self): 120 | super().__init__("Cloudflare detected unusual behaviour") 121 | 122 | 123 | class UserNotLoggedInError(Exception): 124 | """Exception raised when a user tries to perform an action that requires being logged in.""" 125 | 126 | def __init__(self): 127 | super().__init__("Function can only be used if the user is logged in") 128 | 129 | 130 | class ProxiesNoLongerSupportedError(Exception): 131 | """Exception raised when a user tries to use proxies.""" 132 | 133 | def __init__(self): 134 | super().__init__( 135 | "Since using Proxies results in an unreliable experience, they are no longer supported." 136 | ) 137 | -------------------------------------------------------------------------------- /aisexplorer/Proxy.py: -------------------------------------------------------------------------------- 1 | import random 2 | import sys 3 | import time 4 | import lxml.html as lh 5 | import requests 6 | import pandas as pd 7 | import collections 8 | import warnings 9 | from typing import Union, Iterable, Optional, Dict 10 | 11 | 12 | def check_valid_ip(string: str) -> bool: 13 | """Check if a given string is a valid IP address.""" 14 | parts = string.strip().split(".") 15 | return len(parts) == 4 and all( 16 | part.isnumeric() and 0 <= int(part) <= 255 for part in parts 17 | ) 18 | 19 | 20 | def convert_str_to_bool(string: str) -> bool: 21 | """Convert a string to a boolean value based on predefined mappings.""" 22 | return {"yes": True, "": False, "no": False}.get(string, False) 23 | 24 | 25 | def clean_dataframe_column(df: pd.DataFrame, column_name: str) -> pd.DataFrame: 26 | """Clean a specific column in a DataFrame by converting strings to boolean values.""" 27 | if column_name in df.columns: 28 | df[column_name] = df[column_name].apply(convert_str_to_bool) 29 | return df 30 | 31 | 32 | class FreeProxy: 33 | """A class to manage fetching and filtering free proxy lists.""" 34 | 35 | def __init__( 36 | self, 37 | timeout: float = 0.5, 38 | rand: bool = True, 39 | https: bool = True, 40 | refresh_after: int = 900, 41 | verbose: bool = False, 42 | **filters, 43 | ): 44 | self.timeout = timeout 45 | self.random = rand 46 | self.https = https 47 | self.refresh_after = refresh_after 48 | self.verbose = verbose 49 | self.filters = filters 50 | self.proxies = None 51 | self.fetched_at = None 52 | self.filtered_df = None 53 | 54 | def verbose_print(self, message: str): 55 | """Print a message if verbose mode is enabled.""" 56 | if self.verbose: 57 | print(message) 58 | 59 | def series_to_proxy(self, series: pd.Series) -> Dict[str, str]: 60 | """Convert a series from a DataFrame to a proxy dictionary.""" 61 | protocol = "https" if self.https else "http" 62 | return {protocol: f"{series.name}:{series['port']}"} 63 | 64 | def fetch_proxy_list(self): 65 | """Fetch the list of proxies from the web and clean the DataFrame.""" 66 | try: 67 | page = requests.get("https://www.sslproxies.org") 68 | doc = lh.fromstring(page.content) 69 | list_proxies = [] 70 | tr_elements = doc.xpath('//*[@id="list"]//tr') 71 | for tr_element in tr_elements: 72 | if ( 73 | check_valid_ip(tr_element[0].text_content()) 74 | and tr_element[0].text_content() is not None 75 | ): 76 | dict_tmp = {} 77 | for counter, attribute in enumerate( 78 | [ 79 | "ip_address", 80 | "port", 81 | "country_code", 82 | "country", 83 | "anonymity", 84 | "google", 85 | "https", 86 | "last_checked", 87 | ] 88 | ): 89 | dict_tmp[attribute] = tr_element[counter].text_content() 90 | list_proxies.append(dict_tmp) 91 | self.proxies = pd.DataFrame(list_proxies).set_index("ip_address") 92 | self.proxies = clean_dataframe_column(self.proxies, "google") 93 | self.proxies = clean_dataframe_column(self.proxies, "https") 94 | self.fetched_at = time.time() 95 | except requests.exceptions.RequestException as e: 96 | print(e) 97 | sys.exit(1) 98 | 99 | def get_filtered_proxies(self) -> pd.DataFrame: 100 | """Filter the DataFrame of proxies based on the given filters.""" 101 | if self.proxies is None: 102 | self.fetch_proxy_list() 103 | elif time.time() - self.fetched_at >= self.refresh_after: 104 | self.fetch_proxy_list() 105 | 106 | conditions = [] 107 | for key, value in self.filters.items(): 108 | if isinstance(value, bool): 109 | conditions.append(f"(self.proxies['{key}'] == {value})") 110 | elif isinstance(value, str): 111 | conditions.append(f"(self.proxies['{key}'] == '{value}')") 112 | elif isinstance(value, Iterable) and not isinstance(value, (str, bytes)): 113 | conditions.append(f"(self.proxies['{key}'].isin({list(value)}))") 114 | 115 | filter_str = " & ".join(conditions) 116 | return self.proxies.query(filter_str) if filter_str else self.proxies 117 | 118 | def find_working_proxy(self) -> Optional[Dict[str, str]]: 119 | """Find a working proxy from the filtered DataFrame.""" 120 | self.filtered_df = self.get_filtered_proxies() 121 | if self.filtered_df.empty: 122 | return None 123 | 124 | proxy_list = ( 125 | self.filtered_df.sample(frac=1) if self.random else self.filtered_df 126 | ) 127 | for _, proxy_series in proxy_list.iterrows(): 128 | proxy = self.series_to_proxy(proxy_series) 129 | if self.check_if_proxy_is_working(proxy): 130 | return proxy 131 | return None 132 | 133 | def check_if_proxy_is_working(self, proxy: Dict[str, str]) -> bool: 134 | """Check if a given proxy is working.""" 135 | protocol = "https" if self.https else "http" 136 | try: 137 | r = requests.get( 138 | f"{protocol}://github.com/reyemb/AISExplorer/", 139 | proxies=proxy, 140 | timeout=self.timeout, 141 | ) 142 | self.verbose_print(f"Proxy {proxy} returned status code {r.status_code}") 143 | return r.status_code == 200 144 | except Exception as e: 145 | self.verbose_print(f"Proxy {proxy} failed with exception {e}") 146 | return False 147 | 148 | def get(self) -> Optional[Dict[str, str]]: 149 | """Get a working proxy.""" 150 | working_proxy = self.find_working_proxy() 151 | if working_proxy: 152 | return working_proxy 153 | 154 | warnings.warn("No working proxies found. Expanding search.") 155 | self.filters.pop("prefered_country", None) 156 | self.filters.pop("prefered_country_code", None) 157 | return self.find_working_proxy() 158 | -------------------------------------------------------------------------------- /aisexplorer/Filters.py: -------------------------------------------------------------------------------- 1 | import urllib.parse 2 | from collections.abc import Iterable 3 | from aisexplorer.Exceptions import ( 4 | NotSupportedKeyError, 5 | NotSupportedKeyTypeError, 6 | NotSupportedArgumentType, 7 | ) 8 | 9 | 10 | class BaseFilter: 11 | """Base class for all filters.""" 12 | 13 | def __init__(self, key, value, expected_type): 14 | if not isinstance(value, expected_type): 15 | raise NotSupportedKeyTypeError(key, type(value), expected_type) 16 | self.key = key 17 | self.value = value 18 | 19 | def to_query(self): 20 | raise NotImplementedError("Must implement to_query in subclasses.") 21 | 22 | 23 | class StrFilter(BaseFilter): 24 | """Filter for string type queries.""" 25 | 26 | dict_var = { 27 | "vessel_name": "shipname", 28 | "destination_port": "recognized_next_port_in", 29 | "reported_dest": "reported_destinations", 30 | "callsign": "callsign", 31 | "current_port": "current_port_in", 32 | } 33 | 34 | def __init__(self, key, value): 35 | super().__init__(key, value, str) 36 | 37 | def to_query(self): 38 | query_key = self.dict_var[self.key] 39 | if self.key in ["vessel_name", "recognized_next_port_in"]: 40 | operator = "begins" 41 | else: 42 | operator = "eq" 43 | return f"&{query_key}|{operator}|{query_key}={self.value}" 44 | 45 | 46 | class SliderFilter: 47 | _dict_var = { 48 | "lon": "lon_of_latest_position_between", 49 | "lat": "lat_of_latest_position_between", 50 | "latest_report": "time_of_latest_position_between", 51 | "speed": "speed_between", 52 | "course": "course_between", 53 | "dwt": "dwt_between", 54 | "built": "year_of_build_between", 55 | "length": "length_between", 56 | "width": "width_between", 57 | "draught": "draught_between", 58 | } 59 | 60 | def __init__(self, key: str, values: Iterable): 61 | if not isinstance(values, Iterable) or isinstance(values, str): 62 | raise NotSupportedArgumentType( 63 | f"The values for {key} must be an iterable of two numbers." 64 | ) 65 | if len(values) != 2: 66 | raise ValueError( 67 | f"The values for {key} must be an iterable of exactly two elements." 68 | ) 69 | self.key = key 70 | self.value = [str(value) for value in values] 71 | 72 | def to_query(self) -> str: 73 | query_key = self._dict_var.get(self.key) 74 | if query_key is None: 75 | raise NotSupportedKeyError( 76 | f"The key '{self.key}' is not supported for a SliderFilter." 77 | ) 78 | 79 | if self.key == "latest_report": 80 | operator = "gte" 81 | elif self.key == "course": 82 | operator = "range_circle" 83 | else: 84 | operator = "range" 85 | 86 | return f"&{query_key}|{operator}|{query_key}={','.join(self.value)}" 87 | 88 | 89 | class IntFilter: 90 | _dict_var = { 91 | "imo": "imo", 92 | "emi": "emi", 93 | "mmsi": "mmsi", 94 | } 95 | 96 | def __init__(self, key: str, value: int): 97 | if not isinstance(value, int): 98 | raise NotSupportedKeyTypeError(f"The value for {key} must be an integer.") 99 | self.key = key 100 | self.value = value 101 | 102 | def to_query(self) -> str: 103 | query_key = self._dict_var.get(self.key) 104 | if query_key is None: 105 | raise NotSupportedKeyError( 106 | f"The key '{self.key}' is not supported for an IntFilter." 107 | ) 108 | return f"&{query_key}|eq|{query_key}={self.value}" 109 | 110 | 111 | class ListFilter: 112 | _dict_var = { 113 | "flag": "flag_in", 114 | "vessel_type": "ship_type_in", 115 | "global_area": "area_in", 116 | "local_area": "area_local_in", 117 | "nav_status": "navigational_status_in", 118 | "current_port_country": "current_port_country_in", 119 | "fleets": "fleet_in", 120 | } 121 | 122 | def __init__(self, key: str, value): 123 | if not isinstance(value, (list, dict, str)): 124 | raise NotSupportedKeyTypeError( 125 | f"The value for {key} must be a list, dictionary, or string." 126 | ) 127 | self.key = key 128 | self.operator = "in" 129 | if isinstance(value, dict): 130 | self.value = value.get("values") 131 | self.operator = value.get("operator", "in") 132 | else: 133 | self.value = value 134 | 135 | def to_query(self) -> str: 136 | query_key = self._dict_var.get(self.key) 137 | if query_key is None: 138 | raise NotSupportedKeyError( 139 | f"The key '{self.key}' is not supported for a ListFilter." 140 | ) 141 | if isinstance(self.value, list): 142 | value_str = ",".join(map(str, self.value)) 143 | else: 144 | value_str = str(self.value) 145 | return f"&{query_key}|{self.operator}|{value_str}" 146 | 147 | 148 | class FleetFilter: 149 | def __init__(self, user_fleets: Iterable[Iterable[str]]): 150 | if not all( 151 | isinstance(fleet, (list, tuple)) and len(fleet) == 2 152 | for fleet in user_fleets 153 | ): 154 | raise ValueError( 155 | "user_fleets must be an iterable of iterables each with two strings." 156 | ) 157 | self.user_fleets = user_fleets 158 | 159 | def to_referer_query(self) -> str: 160 | fleet_names = ",".join( 161 | urllib.parse.quote_plus(fleet[1]) for fleet in self.user_fleets 162 | ) 163 | fleet_ids = ",".join( 164 | urllib.parse.quote_plus(fleet[0]) for fleet in self.user_fleets 165 | ) 166 | return f"&fleet_in|in|{fleet_names}|fleet_in={fleet_ids}" 167 | 168 | def to_request_query(self) -> str: 169 | fleet_ids = ",".join( 170 | urllib.parse.quote_plus(fleet[0]) for fleet in self.user_fleets 171 | ) 172 | return f"&fleet_in={fleet_ids}" 173 | 174 | 175 | class Filters: 176 | _possible_filters = { 177 | "imo": IntFilter, 178 | "emi": IntFilter, 179 | "mmsi": IntFilter, 180 | "vessel_name": StrFilter, 181 | "callsign": StrFilter, 182 | "current_port": StrFilter, 183 | "reported_dest": StrFilter, 184 | "destination_port": StrFilter, 185 | "latest_report": SliderFilter, 186 | "lat": SliderFilter, 187 | "lon": SliderFilter, 188 | "speed": SliderFilter, 189 | "course": SliderFilter, 190 | "dwt": SliderFilter, 191 | "built": SliderFilter, 192 | "length": SliderFilter, 193 | "width": SliderFilter, 194 | "draught": SliderFilter, 195 | "flag": ListFilter, 196 | "vessel_type": ListFilter, 197 | "global_area": ListFilter, 198 | "local_area": ListFilter, 199 | "nav_status": ListFilter, 200 | "current_port_country": ListFilter, 201 | "fleets": ListFilter, 202 | } 203 | 204 | def __init__(self, **kwargs: dict): 205 | self.filters = { 206 | key: filter_class(key, value) 207 | for key, value in kwargs.items() 208 | if (filter_class := self._possible_filters.get(key)) 209 | } 210 | 211 | unsupported_keys = set(kwargs) - self.filters.keys() 212 | if unsupported_keys: 213 | raise NotSupportedKeyError(f"Unsupported filter keys: {unsupported_keys}.") 214 | 215 | def to_query(self, ignore_filter=None) -> str: 216 | """Generate a query string from filters, excluding any specified in ignore_filter.""" 217 | if ignore_filter is not None: 218 | if isinstance(ignore_filter, str): 219 | ignore_filter = {ignore_filter} 220 | elif not isinstance(ignore_filter, Iterable) or isinstance( 221 | ignore_filter, str 222 | ): 223 | raise ValueError( 224 | "ignore_filter must be a string or an iterable of strings." 225 | ) 226 | 227 | query_parts = [ 228 | filter_instance.to_query() 229 | for key, filter_instance in self.filters.items() 230 | if ignore_filter is None or key not in ignore_filter 231 | ] 232 | return "&".join(query_parts).replace("&&", "&") 233 | -------------------------------------------------------------------------------- /aisexplorer/AIS.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import pandas as pd 3 | import urllib 4 | import collections 5 | import json 6 | import lxml.html as lh 7 | import warnings 8 | 9 | from requests.exceptions import ConnectionError 10 | from aisexplorer.Exceptions import ( 11 | NotSupportedParameterTypeError, 12 | ProxiesNoLongerSupportedError, 13 | NotSupportedParameterError, 14 | CloudflareError, 15 | UserNotLoggedInError, 16 | NoResultsError, 17 | ) 18 | from aisexplorer.Proxy import FreeProxy 19 | from aisexplorer.Filters import Filters, FleetFilter 20 | from tenacity import retry, stop_after_attempt, wait_fixed 21 | 22 | 23 | def raise_no_results_error(retry_state): 24 | """Raise a NoResultsError after a certain number of attempts. 25 | 26 | Args: 27 | retry_state (tenacity.RetryCallState): The current state of the retry logic. 28 | 29 | Raises: 30 | NoResultsError: If no results are obtained after the specified attempts. 31 | """ 32 | attempt_num = retry_state.attempt_number 33 | error_message = ( 34 | f"After {attempt_num} attempts still no results are given. " 35 | f"If you think this is an error in the module raise an issue at " 36 | f"https://github.com/reyemb/AISExplorer " 37 | ) 38 | raise NoResultsError(error_message) 39 | 40 | 41 | class AIS: 42 | """A class to interact with the AISExplorer API with options for proxy and retry logic. 43 | 44 | Attributes: 45 | return_df (bool): If True, return a pandas DataFrame. 46 | return_total_count (bool): If True, return the total count of results. 47 | verbose (bool): If True, enable verbose output. 48 | columns (str or list): Column names to include in the results. 49 | columns_excluded (list): Column names to exclude from the results. 50 | print_query (bool): If True, print the query. 51 | logged_in (bool): If False, the user is not logged in. 52 | session (requests.Session): The session for HTTP requests. 53 | proxy (bool): If True, use a proxy. 54 | burned_proxies (list): List of proxies that have been used and are no longer viable. 55 | """ 56 | 57 | retry_options = { 58 | "stop": stop_after_attempt(10), 59 | "wait": wait_fixed(15), 60 | "retry_error_callback": raise_no_results_error, 61 | } 62 | 63 | def __init__( 64 | self, 65 | proxy=False, 66 | verbose=False, 67 | columns="all", 68 | columns_excluded=None, 69 | print_query=False, 70 | num_retries=10, 71 | seconds_wait=15, 72 | filter_config={}, 73 | return_df=False, 74 | return_total_count=False, 75 | **proxy_config, 76 | ): 77 | """Initializes the AIS class with provided configurations.""" 78 | self.update_retry_options(num_retries, seconds_wait) 79 | self.initialize_attributes( 80 | return_df, 81 | return_total_count, 82 | verbose, 83 | columns, 84 | columns_excluded, 85 | print_query, 86 | ) 87 | self.configure_session(proxy, verbose, proxy_config) 88 | self.set_column_url() 89 | self.set_filters(filter_config=filter_config) 90 | 91 | def update_retry_options(self, num_retries, seconds_wait): 92 | """Update the retry options if they differ from the defaults.""" 93 | if num_retries != 10: 94 | self.retry_options["stop"] = stop_after_attempt(num_retries) 95 | if seconds_wait != 15: 96 | self.retry_options["wait"] = wait_fixed(seconds_wait) 97 | 98 | def configure_session(self, proxy, verbose, proxy_config): 99 | """Configure the session with proxy settings and headers.""" 100 | self.session = requests.Session() 101 | self.session.headers.update( 102 | { 103 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", 104 | "Vessel-Image": "00b3ac45291acfd4e2e0dc4e46b24ec56c05", 105 | } 106 | ) 107 | if proxy: 108 | raise ProxiesNoLongerSupportedError() 109 | self.setup_proxy(proxy_config, verbose=verbose) 110 | else: 111 | self.proxy = False 112 | 113 | def setup_proxy(self, proxy_config, verbose): 114 | """Set up the proxy using the provided configuration.""" 115 | self.verbose_print("Searching for proxy...") 116 | self.freeproxy = ( 117 | FreeProxy(**proxy_config, verbose=verbose) 118 | if proxy_config 119 | else FreeProxy(verbose=verbose) 120 | ) 121 | self.session.proxies = self.freeproxy.get() 122 | self.verbose_print("Proxy found...") 123 | self.proxy = True 124 | self.burned_proxies = [] 125 | 126 | def initialize_attributes( 127 | self, 128 | return_df, 129 | return_total_count, 130 | verbose, 131 | columns, 132 | columns_excluded, 133 | print_query, 134 | ): 135 | """Initialize instance attributes.""" 136 | self.return_df = return_df 137 | self.return_total_count = return_total_count 138 | self.verbose = verbose 139 | self.columns = columns 140 | self.columns_excluded = columns_excluded 141 | self.print_query = print_query 142 | self.logged_in = False 143 | 144 | def login(self, email, password, use_proxy=False): 145 | """Attempt to log in to the AIS service. 146 | 147 | Args: 148 | email (str): The email address for login. 149 | password (str): The password for login. 150 | use_proxy (bool): Flag to determine if a proxy should be used for login. 151 | 152 | Raises: 153 | Exception: If the login fails. 154 | """ 155 | if use_proxy and self.proxy: 156 | warnings.warn( 157 | "Login with proxy can be dangerous because of man-in-the-middle attacks. " 158 | "Although it is possible to use it, it is not recommended." 159 | ) 160 | 161 | login_payload = { 162 | "_method": "POST", 163 | "email": email, 164 | "password": password, 165 | } 166 | login_headers = { 167 | "accept": "*/*", 168 | "accept-language": "en-GB,en;q=0.6", 169 | "content-type": "application/x-www-form-urlencoded; charset=UTF-8", 170 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", 171 | "vessel-image": "00a6f77ecb46da49c92b753fa98af9bed230", 172 | "x-requested-with": "XMLHttpRequest", 173 | } 174 | 175 | response = self.session.post( 176 | "https://www.marinetraffic.com/en/users/ajax_login", 177 | data=login_payload, 178 | headers=login_headers, 179 | proxies=self.session.proxies if use_proxy else None, 180 | ) 181 | 182 | if response.status_code == 200: 183 | self.verbose_print("Login successful") 184 | self.logged_in = True 185 | else: 186 | self.verbose_print("Login failed. Check credentials and try again.") 187 | raise Exception( 188 | str(response.status_code) 189 | + "Login failed. Check credentials and try again." 190 | + response.text 191 | ) # 192 | 193 | def _ensure_logged_in(self): 194 | """Ensures that the user is logged in. 195 | 196 | Raises: 197 | UserNotLoggedInError: If the user is not logged in. 198 | """ 199 | if not self.logged_in: 200 | raise UserNotLoggedInError("User must be logged in to perform this action.") 201 | 202 | def get_fleets(self): 203 | """Fetches fleets data from the AIS service. 204 | 205 | Raises: 206 | UserNotLoggedInError: If the user is not logged in. 207 | Exception: If the request fails with a non-200 status code. 208 | 209 | Returns: 210 | dict: The JSON response containing fleets data if the request is successful. 211 | """ 212 | self._ensure_logged_in() 213 | response = self.session.get( 214 | "https://www.marinetraffic.com/en/search/fleetList", 215 | headers={ 216 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", 217 | "vessel-image": "00b3ac45291acfd4e2e0dc4e46b24ec56c05", 218 | "x-requested-with": "XMLHttpRequest", 219 | }, 220 | ) 221 | if response.status_code == 200: 222 | self.verbose_print("Fetching fleets successful") 223 | return response.json() 224 | else: 225 | raise Exception( 226 | f"Something went wrong! {response.status_code}-{response.text}" 227 | ) 228 | 229 | # @retry(**retry_options) 230 | def get_vessels_by_fleet_id(self, fleet_id: str): 231 | self._ensure_logged_in() 232 | fleets = self.get_fleets() 233 | for fleet in fleets: 234 | if fleet[0] == fleet_id: 235 | return self.get_data(fleets_filter=FleetFilter([[fleet[0], fleet[1]]])) 236 | raise Exception("Fleet not found") 237 | 238 | # @retry(**retry_options) 239 | def get_vessels_by_fleet_name(self, fleet_name: str): 240 | self._ensure_logged_in() 241 | fleets = self.get_fleets() 242 | for fleet in fleets: 243 | if fleet[1] == fleet_name: 244 | return self.get_data(fleets_filter=FleetFilter([[fleet[0], fleet[1]]])) 245 | raise Exception("Fleet not found") 246 | 247 | # @retry(**retry_options) 248 | def get_vessels_in_all_fleets(self): 249 | self._ensure_logged_in() 250 | if not self.logged_in: 251 | raise UserNotLoggedInError() 252 | fleets = self.get_fleets() 253 | return self.get_data(fleets_filter=FleetFilter(fleets)) 254 | 255 | def set_filters(self, filter_config): 256 | """Set the filters for the query based on the provided configuration. 257 | 258 | Args: 259 | filter_config (dict): A dictionary containing filter configurations. 260 | """ 261 | if not isinstance(filter_config, dict): 262 | raise ValueError("filter_config must be a dictionary.") 263 | self.filters = Filters(**filter_config) 264 | 265 | def set_column_url(self): 266 | """Set the column URL parameter for the data request.""" 267 | possible_columns = [ 268 | "time_of_latest_position", 269 | "flag", 270 | "shipname", 271 | "photo", 272 | "recognized_next_port", 273 | "reported_eta", 274 | "reported_destination", 275 | "current_port", 276 | "imo", 277 | "mmsi", 278 | "ship_type", 279 | "show_on_live_map", 280 | "area", 281 | "area_local", 282 | "lat_of_latest_position", 283 | "lon_of_latest_position", 284 | "fleet", 285 | "status", 286 | "eni", 287 | "speed", 288 | "course", 289 | "draught", 290 | "navigational_status", 291 | "year_of_build", 292 | "length", 293 | "width", 294 | "dwt", 295 | "current_port_unlocode", 296 | "current_port_country", 297 | "callsign", 298 | ] 299 | 300 | # Determine the columns to include based on exclusions and specified columns 301 | columns_to_include = self._determine_columns_to_include(possible_columns) 302 | 303 | # Join the columns into a string for the URL 304 | self.columns_url = ",".join(columns_to_include) 305 | 306 | def _determine_columns_to_include(self, possible_columns): 307 | """Determine which columns to include in the data request. 308 | 309 | Args: 310 | possible_columns (list): A list of all possible column names. 311 | 312 | Returns: 313 | list: A list of column names to include in the request. 314 | """ 315 | if self.columns != "all": 316 | columns_selected = ( 317 | self.columns if isinstance(self.columns, list) else [self.columns] 318 | ) 319 | else: 320 | columns_selected = possible_columns 321 | 322 | if self.columns_excluded: 323 | if isinstance(self.columns_excluded, str): 324 | self.columns_excluded = [self.columns_excluded] 325 | 326 | if not all(isinstance(col, str) for col in self.columns_excluded): 327 | raise ValueError("All items in columns_excluded must be strings.") 328 | 329 | columns_selected = [ 330 | col for col in columns_selected if col not in self.columns_excluded 331 | ] 332 | 333 | return columns_selected 334 | 335 | def verbose_print(self, message): 336 | """Print a message if verbose mode is enabled. 337 | 338 | Args: 339 | message (str): The message to be printed. 340 | """ 341 | if self.verbose: 342 | print(message) 343 | 344 | def query_print(self, message): 345 | """Print the query message if query printing is enabled. 346 | 347 | Args: 348 | message (str): The query message to be printed. 349 | """ 350 | if self.print_query: 351 | print(message) 352 | 353 | def check_proxy(self): 354 | """Check if the current proxy is working and renew it if not.""" 355 | if self.proxy and not self.freeproxy.check_if_proxy_is_working( 356 | self.session.proxies 357 | ): 358 | self.verbose_print("Proxy has to be renewed.") 359 | self.renew_proxy() 360 | 361 | def renew_proxy(self): 362 | """Renew the proxy configuration.""" 363 | self.verbose_print("Looking for a new proxy...") 364 | try: 365 | self.verbose_print("Old proxy: " + self.session.proxies["https"]) 366 | self.session.proxies = self.freeproxy.get() 367 | self.verbose_print("New proxy: " + self.session.proxies["https"]) 368 | except Exception as e: 369 | self.verbose_print(f"An error occurred while renewing proxy: {e}") 370 | 371 | @retry(**retry_options) 372 | def get_area_data(self, area): 373 | """ 374 | Retrieves data for a specified geographic area from MarineTraffic. 375 | 376 | Args: 377 | area (str or iterable): The area(s) to retrieve data for. Can be a single string representing a valid area code 378 | or an iterable of area codes. 379 | 380 | Returns: 381 | dict: A dictionary containing the retrieved data. 382 | 383 | Raises: 384 | NotSupportedParameterError: If the provided area code(s) are not valid. 385 | 386 | Example: 387 | To retrieve data for the "Adriatic Sea," use: 388 | >>> data = get_area_data("ADRIA") 389 | 390 | To retrieve data for multiple areas, use an iterable: 391 | >>> data = get_area_data(["ADRIA", "BALTIC"]) 392 | """ 393 | _possible_areas = { 394 | "ADRIA": "Adriatic Sea", 395 | "AG": "Arabian Sea", 396 | "ALASKA": "Alaska", 397 | "ANT": "Antarctica", 398 | "BALTIC": "Baltic Sea", 399 | "BSEA": "Black Sea", 400 | "CARIBS": "Caribbean Sea", 401 | "CASPIAN": "Caspian Sea", 402 | "CCHINA": "Central China", 403 | "CISPAC": "CIS Pacific", 404 | "EAFR": "East Africa", 405 | "EAUS": "East Australia", 406 | "ECCA": "East Coast Central America", 407 | "ECCAN": "East Coast Canada", 408 | "ECI": "East Coast India", 409 | "ECSA": "East Coast South America", 410 | "EMED": "East Mediterranean", 411 | "GLAKES": "Great Lakes", 412 | "INDO": "Indonesia", 413 | "INLSAM": "Inland, South America", 414 | "INLCN": "Inland, China", 415 | "INLEU": "Inland, Europe", 416 | "INLRU": "Inland, Russia", 417 | "INLUS": "Inland, USA", 418 | "JAPAN": "Japan Coast", 419 | "NAUS": "North Australia", 420 | "NCCIS": "North Coast CIS", 421 | "NCHINA": "North China", 422 | "NCSA": "North Coast South America", 423 | "NOATL": "North Atlantic", 424 | "NORDIC": "Norwegian Coast", 425 | "NPAC": "North Pacific", 426 | "PHIL": "Philippines", 427 | "RSEA": "Red Sea", 428 | "SAFR": "South Africa", 429 | "SCHINA": "South China", 430 | "SEASIA": "South-East Asia", 431 | "SIND": "South Indian Ocean", 432 | "SPAC": "South Pacific", 433 | "UKC": "UK Coast & Atlantic", 434 | "USEC": "US East Coast", 435 | "USG": "Gulf of Mexico", 436 | "USWC": "US West Coast", 437 | "WAFR": "West Africa", 438 | "WAUS": "West Australia", 439 | "WCCA": "West Coast Central America", 440 | "WCCAN": "West Coast Canada", 441 | "WCI": "West Coast India", 442 | "WCSA": "West South America", 443 | "WMED": "West Mediterranean", 444 | } 445 | if isinstance(area, str): 446 | if area not in _possible_areas.keys(): 447 | raise NotSupportedParameterError("area", _possible_areas.keys, area) 448 | areas_long = urllib.parse.quote_plus(area) 449 | area_short = area 450 | if isinstance(area, collections.abc.Iterable) and not isinstance( 451 | area, (str, bytes) 452 | ): 453 | for element in area: 454 | if element not in _possible_areas: 455 | raise NotSupportedParameterError( 456 | "area", _possible_areas.keys(), element 457 | ) 458 | areas_long = ",".join( 459 | [urllib.parse.quote_plus(_possible_areas[element]) for element in area] 460 | ) 461 | area_short = ",".join(area) 462 | 463 | request_url = ( 464 | f"https://www.marinetraffic.com/en/reports?asset_type=vessels&columns={self.columns_url}" 465 | f"&area_in|in|{areas_long}|area_in={area_short}{self.filters.to_query(ignore_filter='global_area')}" 466 | ) 467 | referer_url = ( 468 | f"https://www.marinetraffic.com/en/data/?asset_type=vessels&columns={self.columns_url}" 469 | f"&area_in|in|{areas_long}|area_in={area_short}{self.filters.to_query(ignore_filter='global_area')}" 470 | ) 471 | self.query_print("referer_url: " + referer_url) 472 | self.query_print("request_url: " + request_url) 473 | return self.return_response(request_url, referer_url) 474 | 475 | @retry(**retry_options) 476 | def get_location(self, mmsi): 477 | """ 478 | Retrieves location data for a vessel by its MMSI (Maritime Mobile Service Identity) from MarineTraffic. 479 | 480 | Args: 481 | mmsi (int or str): The MMSI of the vessel to retrieve location data for. 482 | 483 | Returns: 484 | dict: A dictionary containing the retrieved location data. 485 | 486 | Example: 487 | To retrieve location data for a vessel with MMSI 211281610, use: 488 | >>> location_data = get_location(211281610) 489 | """ 490 | if isinstance(mmsi, int): 491 | mmsi = str(mmsi) 492 | request_url = ( 493 | f"https://www.marinetraffic.com/en/reports?asset_type=vessels&columns={self.columns_url}" 494 | f"&mmsi|eq|mmsi={mmsi}{self.filters.to_query(ignore_filter='mmsi')}" 495 | ) 496 | 497 | referer_url = ( 498 | f"https://www.marinetraffic.com/en/data/?asset_type=vessels&columns={self.columns_url}" 499 | f"&mmsi|eq|mmsi={mmsi}{self.filters.to_query(ignore_filter='mmsi')}" 500 | ) 501 | self.query_print("referer_url: " + referer_url) 502 | self.query_print("request_url: " + request_url) 503 | return self.return_response(request_url, referer_url) 504 | 505 | def get_data(self, use_Filters=False, fleets_filter=None): 506 | """ 507 | Retrieves data from MarineTraffic based on specified filters and fleet options. 508 | 509 | Args: 510 | use_Filters (bool, optional): Whether to use the configured filters. Defaults to False. 511 | fleets_filter (FleetsFilter, optional): Fleet-specific filter to apply. Defaults to None. 512 | 513 | Returns: 514 | dict or str: If successful, returns the retrieved data as a dictionary. If there is an issue, returns an error message as a string. 515 | 516 | Example: 517 | To retrieve data using filters: 518 | >>> data = get_data(use_Filters=True) 519 | 520 | To retrieve data with fleet-specific filtering: 521 | >>> fleets_filter = FleetsFilter(...) 522 | >>> data = get_data(use_Filters=True, fleets_filter=fleets_filter) 523 | """ 524 | if use_Filters: 525 | if fleets_filter is None: 526 | request_url = ( 527 | f"https://www.marinetraffic.com/en/reports?asset_type=vessels&columns={self.columns_url}" 528 | f"{self.filters.to_query()}" 529 | ) 530 | referer_url = ( 531 | f"https://www.marinetraffic.com/en/data/?asset_type=vessels&columns={self.columns_url}" 532 | f"{self.filters.to_query()}" 533 | ) 534 | else: 535 | request_url = ( 536 | f"https://www.marinetraffic.com/en/reports?asset_type=vessels&columns={self.columns_url},notes" 537 | f"{self.filters.to_query()}{fleets_filter.to_request_query()}" 538 | ) 539 | referer_url = ( 540 | f"https://www.marinetraffic.com/en/data/?asset_type=vessels&columns={self.columns_url},notes" 541 | f"{self.filters.to_query()}{fleets_filter.to_referer_query()}" 542 | ) 543 | else: 544 | if fleets_filter is None: 545 | request_url = f"https://www.marinetraffic.com/en/reports?asset_type=vessels&columns={self.columns_url}" 546 | referer_url = f"https://www.marinetraffic.com/en/data/?asset_type=vessels&columns={self.columns_url}" 547 | else: 548 | request_url = ( 549 | f"https://www.marinetraffic.com/en/reports?asset_type=vessels&columns={self.columns_url},notes" 550 | f"{fleets_filter.to_request_query()}" 551 | ) 552 | referer_url = ( 553 | f"https://www.marinetraffic.com/en/data/?asset_type=vessels&columns={self.columns_url},notes" 554 | f"{fleets_filter.to_referer_query()}" 555 | ) 556 | self.query_print("referer_url: " + referer_url) 557 | self.query_print("request_url: " + request_url) 558 | self.verbose_print("Getting data...") 559 | return self.return_response(request_url=request_url, referer_url=referer_url) 560 | 561 | @retry(**retry_options) 562 | def get_data_by_url(self, url): 563 | """ 564 | Retrieves data from MarineTraffic using a provided URL. 565 | 566 | Args: 567 | url (str): The URL to retrieve data from. 568 | 569 | Returns: 570 | dict or str: If successful, returns the retrieved data as a dictionary. If there is an issue, returns an error message as a string. 571 | 572 | Example: 573 | To retrieve data from a specific URL: 574 | >>> url = "https://www.marinetraffic.com/en/reports?..." 575 | >>> data = get_data_by_url(url) 576 | """ 577 | referer_url = url 578 | request_url = url.replace("data", "reports") 579 | return self.return_response(request_url, referer_url) 580 | 581 | def return_response(self, request_url, referer_url): 582 | """ 583 | Sends an HTTP request to the specified URL and returns the response data. 584 | 585 | Args: 586 | request_url (str): The URL to send the HTTP request to. 587 | referer_url (str): The URL of the referring page. 588 | 589 | Returns: 590 | dict or str: If the request is successful, returns the response data as a dictionary. If there is an issue, returns an error message as a string. 591 | 592 | Example: 593 | To send an HTTP request and retrieve response data: 594 | >>> request_url = "https://www.marinetraffic.com/en/reports?..." 595 | >>> referer_url = "https://www.marinetraffic.com/en/data/?..." 596 | >>> data = return_response(request_url, referer_url) 597 | """ 598 | try: 599 | self.check_proxy() 600 | response = self.session.get( 601 | request_url, 602 | headers={ 603 | "Referer": referer_url, 604 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", 605 | "vessel-image": "0026501dd5e7cae9b8afd72aa41a3f831929", 606 | "x-requested-with": "XMLHttpRequest", 607 | }, 608 | ) 609 | except ConnectionError as ce: 610 | self.verbose_print("Proxy has died. Looking for new proxy...") 611 | raise ce 612 | except Exception as e: 613 | self.verbose_print(f"An error occurred while sending the request: {e}") 614 | raise e 615 | self.check_response_cloudflare(response) 616 | 617 | self.verbose_print(f"Used proxy: {self.session.proxies}") 618 | if response.status_code == 200: 619 | if self.return_df: 620 | if self.return_total_count: 621 | return ( 622 | pd.DataFrame(json.loads(response.text)["data"]), 623 | json.loads(response.text)["totalCount"], 624 | ) 625 | else: 626 | return pd.DataFrame(json.loads(response.text)["data"]) 627 | else: 628 | if self.return_total_count: 629 | return ( 630 | json.loads(response.text)["data"], 631 | json.loads(response.text)["totalCount"], 632 | ) 633 | else: 634 | return json.loads(response.text)["data"] 635 | else: 636 | return f"Response code: {str(response.status_code)} - headers: {self.session.headers} {response.text} - referer_url: {referer_url} - request_url: {request_url}" 637 | 638 | def check_response_cloudflare(self, response): 639 | """ 640 | Checks if the response indicates Cloudflare protection and raises a CloudflareError if detected. 641 | 642 | Args: 643 | response (requests.Response): The HTTP response object to check. 644 | 645 | Raises: 646 | CloudflareError: If Cloudflare protection is detected in the response. 647 | 648 | Example: 649 | To check a response for Cloudflare protection: 650 | >>> response = self.session.get(request_url, headers={ 651 | ... "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", 652 | ... "vessel-image": "005bf958a6548a79c6d3a42eba493e339624", 653 | ... "x-requested-with": "XMLHttpRequest"}) 654 | >>> check_response_cloudflare(response) 655 | """ 656 | doc = lh.fromstring(response.content) 657 | titles = doc.xpath("//title") 658 | if titles: 659 | title = titles[0].text_content() 660 | if "Cloudflare" in title: 661 | self.verbose_print( 662 | f"Cloudflare has detected unusual behavior. Changing Proxy..." 663 | ) 664 | raise CloudflareError() 665 | -------------------------------------------------------------------------------- /examples/Short_Example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "cbfa678a-8155-489a-b78d-c10187b72f9f", 6 | "metadata": {}, 7 | "source": [ 8 | "

AIS-Explorer

\n", 9 | "

AIS-Explorer is designed to grant free access to AIS-Data

\n", 10 | "

Because the data is scraped from https://www.marinetraffic.com please use the functions with care and responsibility

\n", 11 | "

Installing

\n", 12 | "

Installing is as simple as it could be. Just run pip install aisexplorer " 13 | ] 14 | }, 15 | { 16 | "cell_type": "markdown", 17 | "id": "6767fb84-7de2-4f9d-b777-275909620a33", 18 | "metadata": {}, 19 | "source": [ 20 | "

Get Location by MMIS

\n", 21 | "

Returns a dictionary or pandas DataFrame containing the requested data.

\n", 22 | "

Without cetting the parameter columns or columns_excluded all data will be fetched

" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": 1, 28 | "id": "f89f9f93-9cd2-4c10-8a94-cc1ee3eea9db", 29 | "metadata": { 30 | "pycharm": { 31 | "is_executing": true 32 | } 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "from aisexplorer.AIS import AIS\n" 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": 2, 42 | "id": "bdb20c3e-412a-46b1-99a9-091fbfe3c4a8", 43 | "metadata": { 44 | "pycharm": { 45 | "is_executing": true 46 | } 47 | }, 48 | "outputs": [ 49 | { 50 | "ename": "NoResultsError", 51 | "evalue": "After 10 attempts still no results are given. If you think this is an error in the module raise an issue at https://github.com/reyemb/AISExplorer ", 52 | "output_type": "error", 53 | "traceback": [ 54 | "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", 55 | "\u001b[1;31mNoResultsError\u001b[0m Traceback (most recent call last)", 56 | "Cell \u001b[1;32mIn[2], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m \u001b[43mAIS\u001b[49m\u001b[43m(\u001b[49m\u001b[43mreturn_df\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_location\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m211281610\u001b[39;49m\u001b[43m)\u001b[49m\n", 57 | "File \u001b[1;32mc:\\Users\\Meyer\\OneDrive\\Desktop\\Projekte\\aisexplorer\\AISExplorer\\.venv\\Lib\\site-packages\\tenacity\\__init__.py:289\u001b[0m, in \u001b[0;36mBaseRetrying.wraps..wrapped_f\u001b[1;34m(*args, **kw)\u001b[0m\n\u001b[0;32m 287\u001b[0m \u001b[38;5;129m@functools\u001b[39m\u001b[38;5;241m.\u001b[39mwraps(f)\n\u001b[0;32m 288\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mwrapped_f\u001b[39m(\u001b[38;5;241m*\u001b[39margs: t\u001b[38;5;241m.\u001b[39mAny, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkw: t\u001b[38;5;241m.\u001b[39mAny) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m t\u001b[38;5;241m.\u001b[39mAny:\n\u001b[1;32m--> 289\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mf\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkw\u001b[49m\u001b[43m)\u001b[49m\n", 58 | "File \u001b[1;32mc:\\Users\\Meyer\\OneDrive\\Desktop\\Projekte\\aisexplorer\\AISExplorer\\.venv\\Lib\\site-packages\\tenacity\\__init__.py:379\u001b[0m, in \u001b[0;36mRetrying.__call__\u001b[1;34m(self, fn, *args, **kwargs)\u001b[0m\n\u001b[0;32m 377\u001b[0m retry_state \u001b[38;5;241m=\u001b[39m RetryCallState(retry_object\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m, fn\u001b[38;5;241m=\u001b[39mfn, args\u001b[38;5;241m=\u001b[39margs, kwargs\u001b[38;5;241m=\u001b[39mkwargs)\n\u001b[0;32m 378\u001b[0m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[1;32m--> 379\u001b[0m do \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43miter\u001b[49m\u001b[43m(\u001b[49m\u001b[43mretry_state\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mretry_state\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m 380\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(do, DoAttempt):\n\u001b[0;32m 381\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n", 59 | "File \u001b[1;32mc:\\Users\\Meyer\\OneDrive\\Desktop\\Projekte\\aisexplorer\\AISExplorer\\.venv\\Lib\\site-packages\\tenacity\\__init__.py:322\u001b[0m, in \u001b[0;36mBaseRetrying.iter\u001b[1;34m(self, retry_state)\u001b[0m\n\u001b[0;32m 320\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mstop(retry_state):\n\u001b[0;32m 321\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mretry_error_callback:\n\u001b[1;32m--> 322\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mretry_error_callback\u001b[49m\u001b[43m(\u001b[49m\u001b[43mretry_state\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m 323\u001b[0m retry_exc \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mretry_error_cls(fut)\n\u001b[0;32m 324\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mreraise:\n", 60 | "File \u001b[1;32m~\\OneDrive\\Desktop\\Projekte\\AISExplorer\\AISExplorer\\aisexplorer\\AIS.py:48\u001b[0m, in \u001b[0;36mraise_no_results_error\u001b[1;34m(retry_state)\u001b[0m\n\u001b[0;32m 42\u001b[0m attempt_num \u001b[38;5;241m=\u001b[39m retry_state\u001b[38;5;241m.\u001b[39mattempt_number\n\u001b[0;32m 43\u001b[0m error_message \u001b[38;5;241m=\u001b[39m (\n\u001b[0;32m 44\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mAfter \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mattempt_num\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m attempts still no results are given. \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m 45\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mIf you think this is an error in the module raise an issue at \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m 46\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mhttps://github.com/reyemb/AISExplorer \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m 47\u001b[0m )\n\u001b[1;32m---> 48\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m NoResultsError(error_message)\n", 61 | "\u001b[1;31mNoResultsError\u001b[0m: After 10 attempts still no results are given. If you think this is an error in the module raise an issue at https://github.com/reyemb/AISExplorer " 62 | ] 63 | } 64 | ], 65 | "source": [ 66 | "AIS(return_df=True).get_location(211281610)" 67 | ] 68 | }, 69 | { 70 | "cell_type": "markdown", 71 | "id": "d39b25f6-10a6-4f8c-85e1-e8021b77b07b", 72 | "metadata": {}, 73 | "source": [ 74 | "

Get AREA by area name

\n", 75 | "

Returns a list of dictionaries or a pandas DataFrame

\n", 76 | "

You can see all the area names and the corresponding areas on https://help.marinetraffic.com/hc/en-us/articles/214556408-Areas-of-the-World-How-does-MarineTraffic-segment-them-

\n", 77 | "

Maximum 500 rows

" 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": null, 83 | "id": "84191eff-f2db-441a-a734-ad886ec0e2ea", 84 | "metadata": { 85 | "pycharm": { 86 | "is_executing": true 87 | } 88 | }, 89 | "outputs": [], 90 | "source": [ 91 | "AIS().get_area_data(\"EMED\"); # remove semicolon to see output" 92 | ] 93 | }, 94 | { 95 | "cell_type": "code", 96 | "execution_count": null, 97 | "id": "1c23210f-c346-442f-9401-1b08eeba19f7", 98 | "metadata": { 99 | "pycharm": { 100 | "is_executing": true 101 | } 102 | }, 103 | "outputs": [ 104 | { 105 | "data": { 106 | "text/html": [ 107 | "
\n", 108 | "\n", 121 | "\n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | " \n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " \n", 133 | " \n", 134 | " \n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | " \n", 145 | " \n", 146 | " \n", 147 | " \n", 148 | " \n", 149 | " \n", 150 | " \n", 151 | " \n", 152 | " \n", 153 | " \n", 154 | " \n", 155 | " \n", 156 | " \n", 157 | " \n", 158 | " \n", 159 | " \n", 160 | " \n", 161 | " \n", 162 | " \n", 163 | " \n", 164 | " \n", 165 | " \n", 166 | " \n", 167 | " \n", 168 | " \n", 169 | " \n", 170 | " \n", 171 | " \n", 172 | " \n", 173 | " \n", 174 | " \n", 175 | " \n", 176 | " \n", 177 | " \n", 178 | " \n", 179 | " \n", 180 | " \n", 181 | " \n", 182 | " \n", 183 | " \n", 184 | " \n", 185 | " \n", 186 | " \n", 187 | " \n", 188 | " \n", 189 | " \n", 190 | " \n", 191 | " \n", 192 | " \n", 193 | " \n", 194 | " \n", 195 | " \n", 196 | " \n", 197 | " \n", 198 | " \n", 199 | " \n", 200 | " \n", 201 | " \n", 202 | " \n", 203 | " \n", 204 | " \n", 205 | " \n", 206 | " \n", 207 | " \n", 208 | " \n", 209 | " \n", 210 | " \n", 211 | " \n", 212 | " \n", 213 | " \n", 214 | " \n", 215 | " \n", 216 | " \n", 217 | " \n", 218 | " \n", 219 | " \n", 220 | " \n", 221 | " \n", 222 | " \n", 223 | " \n", 224 | " \n", 225 | " \n", 226 | " \n", 227 | " \n", 228 | " \n", 229 | " \n", 230 | " \n", 231 | " \n", 232 | " \n", 233 | " \n", 234 | " \n", 235 | " \n", 236 | " \n", 237 | " \n", 238 | " \n", 239 | " \n", 240 | " \n", 241 | " \n", 242 | " \n", 243 | " \n", 244 | " \n", 245 | " \n", 246 | " \n", 247 | " \n", 248 | " \n", 249 | " \n", 250 | " \n", 251 | " \n", 252 | " \n", 253 | " \n", 254 | " \n", 255 | " \n", 256 | " \n", 257 | " \n", 258 | " \n", 259 | " \n", 260 | " \n", 261 | " \n", 262 | " \n", 263 | " \n", 264 | " \n", 265 | " \n", 266 | " \n", 267 | " \n", 268 | " \n", 269 | " \n", 270 | " \n", 271 | " \n", 272 | " \n", 273 | " \n", 274 | " \n", 275 | " \n", 276 | " \n", 277 | " \n", 278 | " \n", 279 | " \n", 280 | " \n", 281 | " \n", 282 | " \n", 283 | " \n", 284 | " \n", 285 | " \n", 286 | " \n", 287 | " \n", 288 | " \n", 289 | " \n", 290 | " \n", 291 | " \n", 292 | " \n", 293 | " \n", 294 | " \n", 295 | " \n", 296 | " \n", 297 | " \n", 298 | " \n", 299 | " \n", 300 | " \n", 301 | " \n", 302 | " \n", 303 | " \n", 304 | " \n", 305 | " \n", 306 | " \n", 307 | " \n", 308 | " \n", 309 | " \n", 310 | " \n", 311 | " \n", 312 | " \n", 313 | " \n", 314 | " \n", 315 | " \n", 316 | " \n", 317 | " \n", 318 | " \n", 319 | " \n", 320 | " \n", 321 | " \n", 322 | " \n", 323 | " \n", 324 | " \n", 325 | " \n", 326 | " \n", 327 | " \n", 328 | " \n", 329 | " \n", 330 | " \n", 331 | " \n", 332 | " \n", 333 | " \n", 334 | " \n", 335 | " \n", 336 | " \n", 337 | " \n", 338 | " \n", 339 | " \n", 340 | " \n", 341 | " \n", 342 | " \n", 343 | " \n", 344 | " \n", 345 | " \n", 346 | " \n", 347 | " \n", 348 | " \n", 349 | " \n", 350 | " \n", 351 | " \n", 352 | " \n", 353 | " \n", 354 | " \n", 355 | " \n", 356 | " \n", 357 | " \n", 358 | " \n", 359 | " \n", 360 | " \n", 361 | " \n", 362 | " \n", 363 | " \n", 364 | " \n", 365 | " \n", 366 | " \n", 367 | " \n", 368 | " \n", 369 | " \n", 370 | " \n", 371 | " \n", 372 | " \n", 373 | " \n", 374 | " \n", 375 | " \n", 376 | " \n", 377 | " \n", 378 | " \n", 379 | " \n", 380 | " \n", 381 | " \n", 382 | " \n", 383 | " \n", 384 | " \n", 385 | " \n", 386 | " \n", 387 | " \n", 388 | " \n", 389 | " \n", 390 | " \n", 391 | " \n", 392 | " \n", 393 | " \n", 394 | " \n", 395 | " \n", 396 | " \n", 397 | " \n", 398 | " \n", 399 | " \n", 400 | " \n", 401 | " \n", 402 | " \n", 403 | " \n", 404 | " \n", 405 | " \n", 406 | " \n", 407 | " \n", 408 | " \n", 409 | " \n", 410 | " \n", 411 | " \n", 412 | " \n", 413 | " \n", 414 | "
SHIP_IDIMOMMSICALLSIGNSHIPNAMETYPE_COLORLAST_POSTIMEZONECODE2COUNTRY...LENGTHWIDTHSTATUSETA_UPDATEDDISTANCE_TO_GOAREA_NAMEPORT_IDCURRENT_PORTCOLLECTION_NAMECTA_ROUTE_FORECAST
03715841009613310593000ZCDX4ECLIPSE916996231303BMBermuda...162.522.4ACTIVENone0Aegean Sea73MARMARIS-false
12125379208679240389000SYJZBLUE STAR MYCONOS616996230952GRGreece...141.021.0ACTIVE2023-11-10 13:27:0073Piraeus AreaNoneNone-true
22113527909437239722800SVBK6PSARA GLORY616996231332GRGreece...60.113.6ACTIVE2023-11-10 13:27:0035Aegean SeaNoneNone-true
32146179565041241159000SVBF8BLUE STAR PATMOS616996230973GRGreece...145.923.2ACTIVE2023-11-10 13:27:0026East MediterraneanNoneNone-true
42113859207584239737000SYSVBLUE STAR 2616996227852GRGreece...176.126.2ACTIVE2023-11-10 13:27:0082Piraeus Area1PIRAEUS-true
..................................................................
4951229939509126636019197D5TJ9ETE N716996191071LRLiberia...212.532.2ACTIVE2023-11-10 12:52:00745Aegean SeaNoneNone-true
49620048296364242297850009HA5602OCEAN PARADISE916996228962MTMalta...55.09.2ACTIVENone0Piraeus Area443FLISVOS-false
4972080938412429237529000SXYYAEGAEO316996231112GRGreece...61.59.6ACTIVENone0Aegean SeaNoneNone-false
49837207793306046360209135LBI5EMERALD I816996204102LRLiberia...243.642.0ACTIVENone0East MediterraneanNoneNone-false
4997556309169768241610000SVDA3ICE HAWK816996230852GRGreece...145.922.5ACTIVENone0Aegean SeaNoneNone-false
\n", 415 | "

500 rows × 40 columns

\n", 416 | "
" 417 | ], 418 | "text/plain": [ 419 | " SHIP_ID IMO MMSI CALLSIGN SHIPNAME TYPE_COLOR \\\n", 420 | "0 371584 1009613 310593000 ZCDX4 ECLIPSE 9 \n", 421 | "1 212537 9208679 240389000 SYJZ BLUE STAR MYCONOS 6 \n", 422 | "2 211352 7909437 239722800 SVBK6 PSARA GLORY 6 \n", 423 | "3 214617 9565041 241159000 SVBF8 BLUE STAR PATMOS 6 \n", 424 | "4 211385 9207584 239737000 SYSV BLUE STAR 2 6 \n", 425 | ".. ... ... ... ... ... ... \n", 426 | "495 122993 9509126 636019197 D5TJ9 ETE N 7 \n", 427 | "496 200482 9636424 229785000 9HA5602 OCEAN PARADISE 9 \n", 428 | "497 208093 8412429 237529000 SXYY AEGAEO 3 \n", 429 | "498 372077 9330604 636020913 5LBI5 EMERALD I 8 \n", 430 | "499 755630 9169768 241610000 SVDA3 ICE HAWK 8 \n", 431 | "\n", 432 | " LAST_POS TIMEZONE CODE2 COUNTRY ... LENGTH WIDTH STATUS \\\n", 433 | "0 1699623130 3 BM Bermuda ... 162.5 22.4 ACTIVE \n", 434 | "1 1699623095 2 GR Greece ... 141.0 21.0 ACTIVE \n", 435 | "2 1699623133 2 GR Greece ... 60.1 13.6 ACTIVE \n", 436 | "3 1699623097 3 GR Greece ... 145.9 23.2 ACTIVE \n", 437 | "4 1699622785 2 GR Greece ... 176.1 26.2 ACTIVE \n", 438 | ".. ... ... ... ... ... ... ... ... \n", 439 | "495 1699619107 1 LR Liberia ... 212.5 32.2 ACTIVE \n", 440 | "496 1699622896 2 MT Malta ... 55.0 9.2 ACTIVE \n", 441 | "497 1699623111 2 GR Greece ... 61.5 9.6 ACTIVE \n", 442 | "498 1699620410 2 LR Liberia ... 243.6 42.0 ACTIVE \n", 443 | "499 1699623085 2 GR Greece ... 145.9 22.5 ACTIVE \n", 444 | "\n", 445 | " ETA_UPDATED DISTANCE_TO_GO AREA_NAME PORT_ID \\\n", 446 | "0 None 0 Aegean Sea 73 \n", 447 | "1 2023-11-10 13:27:00 73 Piraeus Area None \n", 448 | "2 2023-11-10 13:27:00 35 Aegean Sea None \n", 449 | "3 2023-11-10 13:27:00 26 East Mediterranean None \n", 450 | "4 2023-11-10 13:27:00 82 Piraeus Area 1 \n", 451 | ".. ... ... ... ... \n", 452 | "495 2023-11-10 12:52:00 745 Aegean Sea None \n", 453 | "496 None 0 Piraeus Area 443 \n", 454 | "497 None 0 Aegean Sea None \n", 455 | "498 None 0 East Mediterranean None \n", 456 | "499 None 0 Aegean Sea None \n", 457 | "\n", 458 | " CURRENT_PORT COLLECTION_NAME CTA_ROUTE_FORECAST \n", 459 | "0 MARMARIS - false \n", 460 | "1 None - true \n", 461 | "2 None - true \n", 462 | "3 None - true \n", 463 | "4 PIRAEUS - true \n", 464 | ".. ... ... ... \n", 465 | "495 None - true \n", 466 | "496 FLISVOS - false \n", 467 | "497 None - false \n", 468 | "498 None - false \n", 469 | "499 None - false \n", 470 | "\n", 471 | "[500 rows x 40 columns]" 472 | ] 473 | }, 474 | "execution_count": 4, 475 | "metadata": {}, 476 | "output_type": "execute_result" 477 | } 478 | ], 479 | "source": [ 480 | "AIS(return_df=True).get_area_data(\"EMED\")" 481 | ] 482 | }, 483 | { 484 | "cell_type": "markdown", 485 | "metadata": { 486 | "collapsed": false, 487 | "pycharm": { 488 | "name": "#%% md\n" 489 | } 490 | }, 491 | "source": [ 492 | "

Use Proxies

" 493 | ] 494 | }, 495 | { 496 | "cell_type": "code", 497 | "execution_count": null, 498 | "metadata": { 499 | "collapsed": false, 500 | "pycharm": { 501 | "name": "#%%\n" 502 | } 503 | }, 504 | "outputs": [ 505 | { 506 | "data": { 507 | "text/plain": [ 508 | "{'https': '20.219.180.105:3129'}" 509 | ] 510 | }, 511 | "execution_count": 5, 512 | "metadata": {}, 513 | "output_type": "execute_result" 514 | } 515 | ], 516 | "source": [ 517 | "from aisexplorer.Proxy import FreeProxy\n", 518 | "\n", 519 | "proxy = FreeProxy()\n", 520 | "proxy.get()" 521 | ] 522 | }, 523 | { 524 | "cell_type": "markdown", 525 | "metadata": { 526 | "collapsed": false, 527 | "pycharm": { 528 | "name": "#%% md\n" 529 | } 530 | }, 531 | "source": [ 532 | "

Use Proxies directly with AIS

" 533 | ] 534 | }, 535 | { 536 | "cell_type": "code", 537 | "execution_count": null, 538 | "metadata": { 539 | "collapsed": false, 540 | "pycharm": { 541 | "is_executing": true, 542 | "name": "#%%\n" 543 | } 544 | }, 545 | "outputs": [ 546 | { 547 | "ename": "ProxiesNoLongerSupportedError", 548 | "evalue": "Since using Proxies results in an unreliable experience, they are no longer supported.", 549 | "output_type": "error", 550 | "traceback": [ 551 | "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", 552 | "\u001b[1;31mProxiesNoLongerSupportedError\u001b[0m Traceback (most recent call last)", 553 | "\u001b[1;32mc:\\Users\\Meyer\\OneDrive\\Desktop\\Projekte\\ais-explorer\\AISExplorer\\examples\\Short_Example.ipynb Cell 11\u001b[0m line \u001b[0;36m1\n\u001b[1;32m----> 1\u001b[0m AIS(proxy\u001b[39m=\u001b[39;49m\u001b[39mTrue\u001b[39;49;00m, return_df\u001b[39m=\u001b[39;49m\u001b[39mTrue\u001b[39;49;00m, verbose\u001b[39m=\u001b[39;49m\u001b[39mTrue\u001b[39;49;00m, print_query\u001b[39m=\u001b[39;49m\u001b[39mTrue\u001b[39;49;00m)\u001b[39m.\u001b[39mget_area_data(\u001b[39m\"\u001b[39m\u001b[39mEMED\u001b[39m\u001b[39m\"\u001b[39m)\n", 554 | "File \u001b[1;32m~\\OneDrive\\Desktop\\Projekte\\ais-explorer\\AISExplorer\\aisexplorer\\AIS.py:87\u001b[0m, in \u001b[0;36mAIS.__init__\u001b[1;34m(self, proxy, verbose, columns, columns_excluded, print_query, num_retries, seconds_wait, filter_config, return_df, return_total_count, **proxy_config)\u001b[0m\n\u001b[0;32m 85\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mupdate_retry_options(num_retries, seconds_wait)\n\u001b[0;32m 86\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39minitialize_attributes(return_df, return_total_count, verbose, columns, columns_excluded, print_query)\n\u001b[1;32m---> 87\u001b[0m \u001b[39mself\u001b[39;49m\u001b[39m.\u001b[39;49mconfigure_session(proxy, verbose, proxy_config)\n\u001b[0;32m 88\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mset_column_url()\n\u001b[0;32m 89\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mset_filters(filter_config\u001b[39m=\u001b[39mfilter_config)\n", 555 | "File \u001b[1;32m~\\OneDrive\\Desktop\\Projekte\\ais-explorer\\AISExplorer\\aisexplorer\\AIS.py:106\u001b[0m, in \u001b[0;36mAIS.configure_session\u001b[1;34m(self, proxy, verbose, proxy_config)\u001b[0m\n\u001b[0;32m 101\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39msession\u001b[39m.\u001b[39mheaders\u001b[39m.\u001b[39mupdate({\n\u001b[0;32m 102\u001b[0m \u001b[39m\"\u001b[39m\u001b[39muser-agent\u001b[39m\u001b[39m\"\u001b[39m: \u001b[39m\"\u001b[39m\u001b[39mMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36\u001b[39m\u001b[39m\"\u001b[39m,\n\u001b[0;32m 103\u001b[0m \u001b[39m\"\u001b[39m\u001b[39mVessel-Image\u001b[39m\u001b[39m\"\u001b[39m: \u001b[39m\"\u001b[39m\u001b[39m001b6192a3cc77daab750f70cab85f527b18\u001b[39m\u001b[39m\"\u001b[39m,\n\u001b[0;32m 104\u001b[0m })\n\u001b[0;32m 105\u001b[0m \u001b[39mif\u001b[39;00m proxy:\n\u001b[1;32m--> 106\u001b[0m \u001b[39mraise\u001b[39;00m ProxiesNoLongerSupportedError()\n\u001b[0;32m 107\u001b[0m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39msetup_proxy(proxy_config, verbose\u001b[39m=\u001b[39mverbose)\n\u001b[0;32m 108\u001b[0m \u001b[39melse\u001b[39;00m:\n", 556 | "\u001b[1;31mProxiesNoLongerSupportedError\u001b[0m: Since using Proxies results in an unreliable experience, they are no longer supported." 557 | ] 558 | } 559 | ], 560 | "source": [ 561 | "AIS(proxy=True, return_df=True, verbose=True, print_query=True).get_area_data(\"EMED\")" 562 | ] 563 | }, 564 | { 565 | "cell_type": "markdown", 566 | "metadata": { 567 | "collapsed": false, 568 | "pycharm": { 569 | "name": "#%% md\n" 570 | } 571 | }, 572 | "source": [ 573 | "

Use Filters

" 574 | ] 575 | }, 576 | { 577 | "cell_type": "code", 578 | "execution_count": null, 579 | "metadata": { 580 | "collapsed": false, 581 | "pycharm": { 582 | "is_executing": true, 583 | "name": "#%%\n" 584 | } 585 | }, 586 | "outputs": [ 587 | { 588 | "name": "stdout", 589 | "output_type": "stream", 590 | "text": [ 591 | "referer_url: https://www.marinetraffic.com/en/data/?asset_type=vessels&columns=time_of_latest_position,flag,shipname,photo,recognized_next_port,reported_eta,reported_destination,current_port,imo,mmsi,ship_type,show_on_live_map,area,area_local,lat_of_latest_position,lon_of_latest_position,fleet,status,eni,speed,course,draught,navigational_status,year_of_build,length,width,dwt,current_port_unlocode,current_port_country,callsign&area_in|in|WMED|area_in=WMED&time_of_latest_position_between|gte|time_of_latest_position_between=360,525600&lon_of_latest_position_between|range|lon_of_latest_position_between=-18,-4&lat_of_latest_position_between|range|lat_of_latest_position_between=30,31\n", 592 | "request_url: https://www.marinetraffic.com/en/reports?asset_type=vessels&columns=time_of_latest_position,flag,shipname,photo,recognized_next_port,reported_eta,reported_destination,current_port,imo,mmsi,ship_type,show_on_live_map,area,area_local,lat_of_latest_position,lon_of_latest_position,fleet,status,eni,speed,course,draught,navigational_status,year_of_build,length,width,dwt,current_port_unlocode,current_port_country,callsign&area_in|in|WMED|area_in=WMED&time_of_latest_position_between|gte|time_of_latest_position_between=360,525600&lon_of_latest_position_between|range|lon_of_latest_position_between=-18,-4&lat_of_latest_position_between|range|lat_of_latest_position_between=30,31\n" 593 | ] 594 | }, 595 | { 596 | "data": { 597 | "text/plain": [ 598 | "(Empty DataFrame\n", 599 | " Columns: []\n", 600 | " Index: [],\n", 601 | " 0)" 602 | ] 603 | }, 604 | "execution_count": 7, 605 | "metadata": {}, 606 | "output_type": "execute_result" 607 | } 608 | ], 609 | "source": [ 610 | "ais = AIS(proxy=False, return_df=True, return_total_count=True, verbose=False, print_query=True)\n", 611 | "\n", 612 | "ais.set_filters(filter_config={\n", 613 | " 'latest_report': [360, 525600],\n", 614 | " 'lon': [-18, -4],\n", 615 | " 'lat': [30, 31]\n", 616 | "})\n", 617 | "ais.get_area_data(\"WMED\")\n" 618 | ] 619 | }, 620 | { 621 | "cell_type": "code", 622 | "execution_count": null, 623 | "metadata": { 624 | "collapsed": false, 625 | "pycharm": { 626 | "is_executing": true, 627 | "name": "#%%\n" 628 | } 629 | }, 630 | "outputs": [ 631 | { 632 | "name": "stdout", 633 | "output_type": "stream", 634 | "text": [ 635 | "referer_url: https://www.marinetraffic.com/en/data/?asset_type=vessels&columns=time_of_latest_position,flag,shipname,photo,recognized_next_port,reported_eta,reported_destination,current_port,imo,mmsi,ship_type,show_on_live_map,area,area_local,lat_of_latest_position,lon_of_latest_position,fleet,status,eni,speed,course,draught,navigational_status,year_of_build,length,width,dwt,current_port_unlocode,current_port_country,callsign\n", 636 | "request_url: https://www.marinetraffic.com/en/reports?asset_type=vessels&columns=time_of_latest_position,flag,shipname,photo,recognized_next_port,reported_eta,reported_destination,current_port,imo,mmsi,ship_type,show_on_live_map,area,area_local,lat_of_latest_position,lon_of_latest_position,fleet,status,eni,speed,course,draught,navigational_status,year_of_build,length,width,dwt,current_port_unlocode,current_port_country,callsign\n" 637 | ] 638 | }, 639 | { 640 | "data": { 641 | "text/html": [ 642 | "
\n", 643 | "\n", 656 | "\n", 657 | " \n", 658 | " \n", 659 | " \n", 660 | " \n", 661 | " \n", 662 | " \n", 663 | " \n", 664 | " \n", 665 | " \n", 666 | " \n", 667 | " \n", 668 | " \n", 669 | " \n", 670 | " \n", 671 | " \n", 672 | " \n", 673 | " \n", 674 | " \n", 675 | " \n", 676 | " \n", 677 | " \n", 678 | " \n", 679 | " \n", 680 | " \n", 681 | " \n", 682 | " \n", 683 | " \n", 684 | " \n", 685 | " \n", 686 | " \n", 687 | " \n", 688 | " \n", 689 | " \n", 690 | " \n", 691 | " \n", 692 | " \n", 693 | " \n", 694 | " \n", 695 | " \n", 696 | " \n", 697 | " \n", 698 | " \n", 699 | " \n", 700 | " \n", 701 | " \n", 702 | " \n", 703 | " \n", 704 | " \n", 705 | " \n", 706 | " \n", 707 | " \n", 708 | " \n", 709 | " \n", 710 | " \n", 711 | " \n", 712 | " \n", 713 | " \n", 714 | " \n", 715 | " \n", 716 | " \n", 717 | " \n", 718 | " \n", 719 | " \n", 720 | " \n", 721 | " \n", 722 | " \n", 723 | " \n", 724 | " \n", 725 | " \n", 726 | " \n", 727 | " \n", 728 | " \n", 729 | " \n", 730 | " \n", 731 | " \n", 732 | " \n", 733 | " \n", 734 | " \n", 735 | " \n", 736 | " \n", 737 | " \n", 738 | " \n", 739 | " \n", 740 | " \n", 741 | " \n", 742 | " \n", 743 | " \n", 744 | " \n", 745 | " \n", 746 | " \n", 747 | " \n", 748 | " \n", 749 | " \n", 750 | " \n", 751 | " \n", 752 | " \n", 753 | " \n", 754 | " \n", 755 | " \n", 756 | " \n", 757 | " \n", 758 | " \n", 759 | " \n", 760 | " \n", 761 | " \n", 762 | " \n", 763 | " \n", 764 | " \n", 765 | " \n", 766 | " \n", 767 | " \n", 768 | " \n", 769 | " \n", 770 | " \n", 771 | " \n", 772 | " \n", 773 | " \n", 774 | " \n", 775 | " \n", 776 | " \n", 777 | " \n", 778 | " \n", 779 | " \n", 780 | " \n", 781 | " \n", 782 | " \n", 783 | " \n", 784 | " \n", 785 | " \n", 786 | " \n", 787 | " \n", 788 | " \n", 789 | " \n", 790 | " \n", 791 | " \n", 792 | " \n", 793 | " \n", 794 | " \n", 795 | " \n", 796 | " \n", 797 | " \n", 798 | " \n", 799 | " \n", 800 | " \n", 801 | " \n", 802 | " \n", 803 | " \n", 804 | " \n", 805 | " \n", 806 | " \n", 807 | " \n", 808 | " \n", 809 | " \n", 810 | " \n", 811 | " \n", 812 | " \n", 813 | " \n", 814 | " \n", 815 | " \n", 816 | " \n", 817 | " \n", 818 | " \n", 819 | " \n", 820 | " \n", 821 | " \n", 822 | " \n", 823 | " \n", 824 | " \n", 825 | " \n", 826 | " \n", 827 | " \n", 828 | " \n", 829 | " \n", 830 | " \n", 831 | " \n", 832 | " \n", 833 | " \n", 834 | " \n", 835 | " \n", 836 | " \n", 837 | " \n", 838 | " \n", 839 | " \n", 840 | " \n", 841 | " \n", 842 | " \n", 843 | " \n", 844 | " \n", 845 | " \n", 846 | " \n", 847 | " \n", 848 | " \n", 849 | " \n", 850 | " \n", 851 | " \n", 852 | " \n", 853 | " \n", 854 | " \n", 855 | " \n", 856 | " \n", 857 | " \n", 858 | " \n", 859 | " \n", 860 | " \n", 861 | " \n", 862 | " \n", 863 | " \n", 864 | " \n", 865 | " \n", 866 | " \n", 867 | " \n", 868 | " \n", 869 | " \n", 870 | " \n", 871 | " \n", 872 | " \n", 873 | " \n", 874 | " \n", 875 | " \n", 876 | " \n", 877 | " \n", 878 | " \n", 879 | " \n", 880 | " \n", 881 | " \n", 882 | " \n", 883 | " \n", 884 | " \n", 885 | " \n", 886 | " \n", 887 | " \n", 888 | " \n", 889 | " \n", 890 | " \n", 891 | " \n", 892 | " \n", 893 | " \n", 894 | " \n", 895 | " \n", 896 | " \n", 897 | " \n", 898 | " \n", 899 | " \n", 900 | " \n", 901 | " \n", 902 | " \n", 903 | " \n", 904 | " \n", 905 | " \n", 906 | " \n", 907 | " \n", 908 | " \n", 909 | " \n", 910 | " \n", 911 | " \n", 912 | " \n", 913 | " \n", 914 | " \n", 915 | " \n", 916 | " \n", 917 | " \n", 918 | " \n", 919 | " \n", 920 | " \n", 921 | " \n", 922 | " \n", 923 | " \n", 924 | " \n", 925 | " \n", 926 | " \n", 927 | " \n", 928 | " \n", 929 | " \n", 930 | " \n", 931 | " \n", 932 | " \n", 933 | " \n", 934 | " \n", 935 | " \n", 936 | " \n", 937 | " \n", 938 | " \n", 939 | " \n", 940 | " \n", 941 | " \n", 942 | " \n", 943 | " \n", 944 | " \n", 945 | " \n", 946 | " \n", 947 | " \n", 948 | " \n", 949 | "
SHIP_IDIMOMMSICALLSIGNSHIPNAMETYPE_COLORLAST_POSTIMEZONECODE2COUNTRY...LENGTHWIDTHSTATUSETA_UPDATEDDISTANCE_TO_GOAREA_NAMEPORT_IDCURRENT_PORTCOLLECTION_NAMECTA_ROUTE_FORECAST
03716819241061310627000ZCEF6QUEEN MARY 2616996225621BMBermuda...344.348.7ACTIVENone0Rotterdam Area2033ROTTERDAM BOTLEK-false
156301389811000353136000H3RCEVER GIVEN716990883578PAPanama...399.959.0ACTIVENone0South ChinaNoneNone-false
23716689477438310625000ZCEF2QUEEN ELIZABETH616996227104BMBermuda...294.036.0ACTIVENone0Arabian GulfNoneSALALAH-false
33715841009613310593000ZCDX4ECLIPSE916996231303BMBermuda...162.522.4ACTIVENone0Aegean Sea73MARMARIS-false
44290687729057366904940WYR4481PAUL R.TREGURTHA71699623044-5USUSA...308.132.0ACTIVENone0Lake Erie2497MONROE-false
..................................................................
4951250559601326219029786OZSB2WIND ORCA316995463761DKDenmark...161.349.0ACTIVENone0Rotterdam Area2049SCHIEDAM-false
4962017581010222538071095V7NV8LUNA916569385311MHMarshall Is...114.020.0ACTIVENone0Elbe RiverNoneNone-false
49728410388147443706100003EGC6CROWN IRIS616977976873PAPanama...207.133.2ACTIVENone0Aegean Sea23CHALKIS-false
4981553350219015928XP3539ATHENA91689614026-4DKDenmark...12.04.0ACTIVENone0North Coast South America649CHAGUARAMAS-false
4996821211006324470886000A6E3044DUBAI916996230364AEUAE...160.022.5ACTIVENone0Dubai Area104DUBAI-false
\n", 950 | "

500 rows × 40 columns

\n", 951 | "
" 952 | ], 953 | "text/plain": [ 954 | " SHIP_ID IMO MMSI CALLSIGN SHIPNAME TYPE_COLOR \\\n", 955 | "0 371681 9241061 310627000 ZCEF6 QUEEN MARY 2 6 \n", 956 | "1 5630138 9811000 353136000 H3RC EVER GIVEN 7 \n", 957 | "2 371668 9477438 310625000 ZCEF2 QUEEN ELIZABETH 6 \n", 958 | "3 371584 1009613 310593000 ZCDX4 ECLIPSE 9 \n", 959 | "4 429068 7729057 366904940 WYR4481 PAUL R.TREGURTHA 7 \n", 960 | ".. ... ... ... ... ... ... \n", 961 | "495 125055 9601326 219029786 OZSB2 WIND ORCA 3 \n", 962 | "496 201758 1010222 538071095 V7NV8 LUNA 9 \n", 963 | "497 284103 8814744 370610000 3EGC6 CROWN IRIS 6 \n", 964 | "498 155335 0 219015928 XP3539 ATHENA 9 \n", 965 | "499 682121 1006324 470886000 A6E3044 DUBAI 9 \n", 966 | "\n", 967 | " LAST_POS TIMEZONE CODE2 COUNTRY ... LENGTH WIDTH STATUS \\\n", 968 | "0 1699622562 1 BM Bermuda ... 344.3 48.7 ACTIVE \n", 969 | "1 1699088357 8 PA Panama ... 399.9 59.0 ACTIVE \n", 970 | "2 1699622710 4 BM Bermuda ... 294.0 36.0 ACTIVE \n", 971 | "3 1699623130 3 BM Bermuda ... 162.5 22.4 ACTIVE \n", 972 | "4 1699623044 -5 US USA ... 308.1 32.0 ACTIVE \n", 973 | ".. ... ... ... ... ... ... ... ... \n", 974 | "495 1699546376 1 DK Denmark ... 161.3 49.0 ACTIVE \n", 975 | "496 1656938531 1 MH Marshall Is ... 114.0 20.0 ACTIVE \n", 976 | "497 1697797687 3 PA Panama ... 207.1 33.2 ACTIVE \n", 977 | "498 1689614026 -4 DK Denmark ... 12.0 4.0 ACTIVE \n", 978 | "499 1699623036 4 AE UAE ... 160.0 22.5 ACTIVE \n", 979 | "\n", 980 | " ETA_UPDATED DISTANCE_TO_GO AREA_NAME PORT_ID \\\n", 981 | "0 None 0 Rotterdam Area 2033 \n", 982 | "1 None 0 South China None \n", 983 | "2 None 0 Arabian Gulf None \n", 984 | "3 None 0 Aegean Sea 73 \n", 985 | "4 None 0 Lake Erie 2497 \n", 986 | ".. ... ... ... ... \n", 987 | "495 None 0 Rotterdam Area 2049 \n", 988 | "496 None 0 Elbe River None \n", 989 | "497 None 0 Aegean Sea 23 \n", 990 | "498 None 0 North Coast South America 649 \n", 991 | "499 None 0 Dubai Area 104 \n", 992 | "\n", 993 | " CURRENT_PORT COLLECTION_NAME CTA_ROUTE_FORECAST \n", 994 | "0 ROTTERDAM BOTLEK - false \n", 995 | "1 None - false \n", 996 | "2 SALALAH - false \n", 997 | "3 MARMARIS - false \n", 998 | "4 MONROE - false \n", 999 | ".. ... ... ... \n", 1000 | "495 SCHIEDAM - false \n", 1001 | "496 None - false \n", 1002 | "497 CHALKIS - false \n", 1003 | "498 CHAGUARAMAS - false \n", 1004 | "499 DUBAI - false \n", 1005 | "\n", 1006 | "[500 rows x 40 columns]" 1007 | ] 1008 | }, 1009 | "execution_count": 8, 1010 | "metadata": {}, 1011 | "output_type": "execute_result" 1012 | } 1013 | ], 1014 | "source": [ 1015 | "df, return_total_count = ais.get_data()\n", 1016 | "df" 1017 | ] 1018 | }, 1019 | { 1020 | "cell_type": "code", 1021 | "execution_count": null, 1022 | "id": "5fd9f654", 1023 | "metadata": {}, 1024 | "outputs": [], 1025 | "source": [] 1026 | } 1027 | ], 1028 | "metadata": { 1029 | "kernelspec": { 1030 | "display_name": ".venv", 1031 | "language": "python", 1032 | "name": "python3" 1033 | }, 1034 | "language_info": { 1035 | "codemirror_mode": { 1036 | "name": "ipython", 1037 | "version": 3 1038 | }, 1039 | "file_extension": ".py", 1040 | "mimetype": "text/x-python", 1041 | "name": "python", 1042 | "nbconvert_exporter": "python", 1043 | "pygments_lexer": "ipython3", 1044 | "version": "3.12.1" 1045 | }, 1046 | "vscode": { 1047 | "interpreter": { 1048 | "hash": "e5a0976b86259be7646ecbb2d2099a00b11fa8e90f4fd3d7a6526f3cec68e2c4" 1049 | } 1050 | } 1051 | }, 1052 | "nbformat": 4, 1053 | "nbformat_minor": 5 1054 | } 1055 | --------------------------------------------------------------------------------